Sliding Window
Step 4 in the DSA path · 1 concepts · 6 problems
📘 Learn Sliding Window from zero
The Sliding Window pattern maintains a contiguous range [L, R] over an array or string and slides it instead of re-scanning every subrange, turning an O(n²) brute force into O(n). You expand the right edge to include new elements and contract the left edge when the window breaks a constraint, all while tracking the best window seen. Reach for it whenever a problem asks for the longest / shortest / best contiguous subarray or substring satisfying some condition (a target sum, a character count, or "no duplicates"). Canonical example: Longest Substring Without Repeating Characters on s = "abcabcbb" (answer = 3).✨ Added by the guide to build intuition — not from the source course.
🏗️ Visual walkthrough — trace it step by step
s = a b c a b c b b
idx 0 1 2 3 4 5 6 7
^
LR (L=0, R=0)
seen = {} best = 0We keep a window [L, R] plus a seen map of each character's last index. L is the left edge, R scans rightward. Invariant: s[L..R] always holds only distinct characters, and best records the longest valid window so far.
s = [a] b c a b c b b
idx 0 1 2 3 4 5 6 7
^
LR
window = "a" len = 1
seen = {a:0} best = 1'a' is not in the window, so we extend R and record seen[a]=0, giving best=1. Safe because a single character can never duplicate itself.
s = [a b c] a b c b b
idx 0 1 2 3 4 5 6 7
^ ^
L R
R=1 'b' new: window "ab" len=2 best=2
R=2 'c' new: window "abc" len=3 best=3
seen = {a:0, b:1, c:2} best = 3'b' (R=1) then 'c' (R=2) are both new, so R keeps expanding while L stays pinned at 0. The window grows "ab" (best=2) then "abc" (best=3). No duplicate appeared, so no shrink is required and the invariant still holds.
s = a [b c a] b c b b
idx 0 1 2 3 4 5 6 7
^ ^
L R
seen[a]=0 and 0 >= L(=0), so L = 0+1 = 1
window = "bca" len = 3 best = 3'a' is already in seen at index 0, which is inside the current window. To restore "no duplicates" we jump L = seen[a]+1 = 1, skipping the stale 'a' in one move, then set seen[a]=3. Window "bca" has len 3, so best stays 3.
s = a b [c a b] c b b
idx 0 1 2 3 4 5 6 7
^ ^
L R
seen[b]=1 and 1 >= L(=1), so L = 1+1 = 2
window = "cab" len = 3 best = 3'b' was last seen at index 1, still >= L, so it is a true in-window duplicate. We jump L=2 and set seen[b]=4. We never move L backward — that is exactly why the jump check uses seen[ch] >= L rather than just "ch in seen".
s = a b c [a b c] b b
idx 0 1 2 3 4 5 6 7
^ ^
L R
seen[c]=2 and 2 >= L(=2), so L = 2+1 = 3
window = "abc" len = 3 best = 3Same move for 'c' (last seen at 2): jump L=3, set seen[c]=5. The window slides to "abc" with len 3. Each character is added by R once and skipped by L at most once, which is why the whole scan stays O(n).
s = a b c a b c b [b]
idx 0 1 2 3 4 5 6 7
^
LR
R=6 'b': seen[b]=4 >= L(3) -> L=5 ; "cb" len2
R=7 'b': seen[b]=6 >= L(5) -> L=7 ; "b" len1
best = 3 ANSWER = 3At R=6 the duplicate 'b' (last seen at 4) jumps L=5, window "cb"; we set seen[b]=6. At R=7 another 'b' (now last seen at 6) jumps L=7, window "b". Neither beats best. R has reached the end, so we return best = 3 — the length of "abc". This same expand-then-shrink-on-violation template also powers Minimum Size Subarray Sum, Max Vowels in a Substring, and Max Consecutive Ones III.
🎯 Guided practice
- Easy — Maximum Average Subarray I (fixed window). Given
numsandk, return the maximum average of any contiguous subarray of lengthk.Reasoning: Average is maximized exactly when the sum is maximized (
kis constant), so optimize the sum and divide once at the end. (1) Build the first window:windowSum = sum(nums[0..k-1]); setbest = windowSum. (2) Slideifromkton-1:windowSum += nums[i] - nums[i-k]— add the entrant, subtract the element that fell off the left. (3) After each slide,best = max(best, windowSum). (4) Returnbest / kas a double — in C++/Javabestandkare ints, so cast ((double)best / k) or integer division will silently truncate12.75to12.Trace on
nums = [1,12,-5,-6,50,3],k = 4: first window sum =1+12-5-6 = 2. Slide (add 50, drop 1):2 + 50 - 1 = 51. Slide (add 3, drop 12):51 + 3 - 12 = 42. best =51→ answer51/4 = 12.75. Linear time,O(1)space. Lesson: a fixed window is just a running aggregate you patch as you slide. (Sibling:Maximum Number of Vowels in a Substring of Given Lengthis the identical fixed-window template, but the aggregate is a vowel count — on each slide docnt += isVowel(s[i]) - isVowel(s[i-k]).) - Medium — Max Consecutive Ones III (variable window, "longest"). Given a binary array
numsand integerk, return the length of the longest contiguous subarray of all 1's after flipping at mostkzeros.Reframe (the key move): "flip at most
kzeros to make all 1's" = "find the longest window containing at mostkzeros". That constraint is monotonic, so the variable-window template applies. (1)left = 0,zeros = 0,best = 0. (2) Expand: for eachright, ifnums[right] == 0incrementzeros. (3) Restore the invariant:while zeros > k, ifnums[left] == 0decrementzeros, thenleft += 1. (4) The window[left, right]is now valid; recordbest = max(best, right - left + 1)— after shrinking, because this is a "longest" problem.Trace on
nums = [1,1,0,0,1,1,1,0,1],k = 2: both zeros are admitted by index 3 (window[0..3], zeros = 2, valid, len 4). Indices 4–6 add 1's freely (window[0..6], len 7, best = 7). At index 7 a third zero appears →zeros = 3 > k; shrink, advancingleftfrom 0 past the zero at index 2, droppingzerosback to 2 → window[3..7], len 5. Index 8 → window[3..8], len 6. Final best = 7.O(n)time,O(1)space.Sibling trap — Longest Subarray of 1's After Deleting One Element (LC 1493). This looks like the
k = 1case but the off-by-one is genuinely different, and the difference is load-bearing. You must delete exactly one element, so the answer isright - leftunconditionally (never+ 1), even when the window holds zero zeros. Why: the "at most one zero" window represents the subarray before deletion; removing one element shortens the resulting all-ones run by one, givingwindowSize - 1 = right - left. Concrete check: input[1,1,1]must return 2, not 3 — there is no zero to flip, you are still forced to delete one element. (Contrast: Max Consecutive Ones III withk = 1on[1,1,1]returns 3, because flipping keeps the length.) - Medium — Minimum Size Subarray Sum (variable window, "shortest"). Given a positive-valued array
numsand atarget, return the minimal length of a contiguous subarray with sum ≥target(or 0 if none).The fork from the previous problem: here we want the shortest valid window, so the answer is recorded inside the shrink loop, not after it. (1)
left = 0,sum = 0,best = ∞. (2) Expand:sum += nums[right]. (3)while sum >= target(window is valid): recordbest = min(best, right - left + 1), thensum -= nums[left]; left += 1to try a shorter window. (4) Returnbest == ∞ ? 0 : best. Trace ontarget = 7,nums = [2,3,1,2,4,3]: the window[4,3]at indices 4–5 sums to 7 with length 2, and no length-1 element reaches 7, so best = 2. The reason recording happens inside the loop: every state whilesum >= targetis a valid candidate, and the smallest occurs at the tightest left edge. This is the canonical demonstration of Recognition Pitfall 3 — the all-positive guarantee is what makes the shrink monotonic; drop it and you must switch to prefix-sum + binary search. TheLongest Substring Without Repeating Charactersproblem is the same variable-window machinery with a hash map: extendright, and while the entering character is already in the window, shrink from the left (removing characters) until it is unique again, then record the "longest" lengthright - left + 1after the window is valid — givingO(n)time andO(min(n, |charset|))space.
✨ Added by the guide — work these before the full problem set.
Lessons in this topic
- ○Introduction to Sliding Window Pattern
- ○easy Maximum Average Subarray I
- ○medium Maximum Number of Vowels in a Substring of Given Length
- ○medium Max Consecutive Ones III
- ○medium Longest Subarray of 1's After Deleting One Element
- ○medium Minimum Size Subarray Sum
- ○medium Longest Substring Without Repeating Characters