Sliding window is two-pointers with a constraint: the window represents a candidate, the answer is some aggregate over the window, and you maintain the aggregate incrementally. Recomputing on every shift is the rookie mistake — incremental update is the entire point.
Three flavors
Fixed-size. Window of length k, slide once per step. Maintain sum / max / counter incrementally — O(n). Anything that says “subarray of size k” is this.
Variable-size, expand-then-shrink. Right pointer always advances; left advances until the window is valid. The classic for “longest substring without repeats,” “minimum window covering,” “longest substring with at most k distinct.”
Variable-size with auxiliary structure. Same as above, but the validity predicate needs more than a counter — typically a hash map of frequencies plus a matched count to avoid re-scanning the map every step.
The “matched count” trick
For “minimum window containing all chars of T”: don’t compare the full window counter to the target counter every step (O(alphabet) per step). Instead:
- Track
formed= how many distinct chars in the window have hit their required count. - On expand: increment
window[c]; if it now equalstarget[c], incrementformed. - On shrink: if
window[c]was equal totarget[c]and you’re about to decrement, decrementformedfirst. - Window is valid iff
formed == |target|.
This is what gets you to O(n) instead of O(n Ă— alphabet).
Monotonic deque, for max/min over window
Sliding-window maximum: maintain a deque of indices where values are monotonically decreasing. The front is always the max. Push: pop tail while smaller. Pop front when it falls out of the window. Each index is pushed and popped once → O(n).
This generalizes — any “window aggregate that is order statistic” wants a monotonic deque or a heap (with lazy deletion).
When it doesn’t apply
Sliding window assumes the answer is some function of a contiguous range and the validity predicate is monotone in the window. Non-contiguous (subset, subsequence) → backtracking or DP. Non-monotone (predicate flips on/off as window grows) → it’s still tractable but you can’t assume the “shrink until valid” loop.