Sliding Window
Answer questions about runs of neighbouring items in one pass: slide a window, add what enters, remove what leaves. Fixed and growing windows, traced.
On this page
Your step counter logged a week of walking, in thousands of steps: 4, 2, 7, 1, 8, 3, 5. You want your best stretch of 3 days in a row. The obvious way is to add up days 1 to 3, then days 2 to 4, then days 3 to 5, and so on, each time from scratch.
Look at two neighbouring sums. Days 1 to 3 are 4 + 2 + 7. Days 2 to 4 are 2 + 7 + 1. The 2 + 7 in the middle is shared. Only one day left and one day arrived. So instead of re-adding everything, take the old sum, subtract the day that left, and add the day that arrived.
The idea: a frame that slides
Picture a frame laid over the array that covers a run of neighbouring items: a window. You keep a summary of what is inside it, such as a sum, a count or a set. When the window slides one step right, exactly one item enters and at most one leaves, so you update the summary with two small changes instead of rebuilding it.
Fixed-size windows
When the window size k is fixed, the recipe is short. Add up the first k items. Then, for each new item, add it and subtract the item k places behind it.
sum = steps[0] + ... + steps[k - 1]best = sumfor right from k to n - 1: sum += steps[right] - steps[right - k] best = max(best, sum)- steps04122731485365
- k
- 3
- sum
- 13
- best
- 13
1Add the first 3 days: 4 + 2 + 7 = 13. That is the best so far.
- steps04122731right485365
- k
- 3
- sum
- 10
- best
- 13
2Slide: 1 enters, 4 leaves. 13 + 1 − 4 = 10. Not better.
- steps0412273148right5365
- k
- 3
- sum
- 16
- best
- 16
3Slide: 8 enters, 2 leaves. 10 + 8 − 2 = 16. New best.
- steps041227314853right65
- k
- 3
- sum
- 12
- best
- 16
4Slide: 3 enters, 7 leaves. 16 + 3 − 7 = 12.
- steps04122731485365right
- k
- 3
- sum
- 16
- best
- 16
5Slide: 5 enters, 1 leaves. 12 + 5 − 1 = 16. Ties the best, so
beststays 16. - steps04122731485365
- best
- 16
- windows checked
- 5
6Done. The best 3-day total is 16, first reached on days 3 to 5 (7, 1, 8). Each slide cost one addition and one subtraction instead of re-adding 3 days. With a 30-day window it would still be 2 operations instead of 30.
// Largest sum of k neighbouring items. Assumes 1 <= k <= nums.length.
function bestWindowSum(nums, k) {
let sum = 0;
for (let i = 0; i < k; i++) sum += nums[i];
let best = sum;
for (let right = k; right < nums.length; right++) {
sum += nums[right] - nums[right - k]; // one in, one out
best = Math.max(best, sum);
}
return best;
}
bestWindowSum([4, 2, 7, 1, 8, 3, 5], 3); // 16# Largest sum of k neighbouring items. Assumes 1 <= k <= len(nums).
def best_window_sum(nums, k):
total = sum(nums[:k])
best = total
for right in range(k, len(nums)):
total += nums[right] - nums[right - k] # one in, one out
best = max(best, total)
return best
best_window_sum([4, 2, 7, 1, 8, 3, 5], 3) # 16Windows that grow and shrink
Often the size is not given. The question is "the longest run that obeys a rule" or "the shortest run that reaches a goal". Then the window's two edges move separately, like the same-direction shape in Two Pointers:
- Look at the next item, at
right. - If taking it in would break the rule, move
leftforward, removing items, until it would not. - Take the item in. The window is valid, so record its length if it is the best so far.
Example: find the longest run of letters in "streets" with no letter repeated. The summary is a set of the letters inside the window, so "is this letter already in the window?" costs O(1).
for right from 0 to n - 1: while s[right] is in window: remove s[left] from window; left += 1 add s[right] to window best = max(best, right - left + 1)- s0sleft right1t2r3e4e5t6s
- window
- {s}
- best
- 1
1
senters. No repeats. Window length 1. - s0sleft1tright2r3e4e5t6s
- window
- {s, t}
- best
- 2
2
tenters. Length 2. - s0sleft1t2rright3e4e5t6s
- window
- {s, t, r}
- best
- 3
3
renters. Length 3. - s0sleft1t2r3eright4e5t6s
- window
- {s, t, r, e}
- best
- 4
4
eenters. Length 4: "stre". - s0s1tleft2r3e4eright5t6s
- window
- {t, r, e}
- best
- 4
5The next letter is
e, and aneis already inside. The window must shrink from the left until the oldeis gone. Removes. - s0s1t2rleft3e4eright5t6s
- window
- {r, e}
- best
- 4
6Still an
einside. Removet. - s0s1t2r3eleft4eright5t6s
- window
- {e}
- best
- 4
7Still there. Remove
r. - s0s1t2r3e4eleft right5t6s
- window
- {}
- best
- 4
8Remove the old
e. Now the window is empty and the newecan come in. - s0s1t2r3e4eleft right5t6s
- window
- {e}
- best
- 4
9Add the new
e. Window "e", length 1. Best stays 4. - s0s1t2r3e4eleft5tright6s
- window
- {e, t}
- best
- 4
10
tenters. The earliertalready left, so no clash. Length 2. - s0s1t2r3e4eleft5t6sright
- window
- {e, t, s}
- best
- 4
11
senters. Length 3: "ets". - s0s1t2r3e4e5t6s
- best
- 4
- pointer moves
- 11
12Done: the longest run is "stre", length 4.
rightmoved 7 times andleftmoved 4 times: 11 moves for 7 letters, far fewer than checking all 28 runs.
function longestNoRepeat(s) {
const inWindow = new Set();
let left = 0;
let best = 0;
for (let right = 0; right < s.length; right++) {
while (inWindow.has(s[right])) { // shrink until the rule holds
inWindow.delete(s[left]);
left++;
}
inWindow.add(s[right]);
best = Math.max(best, right - left + 1);
}
return best;
}
longestNoRepeat("streets"); // 4def longest_no_repeat(s):
in_window = set()
left = 0
best = 0
for right in range(len(s)):
while s[right] in in_window: # shrink until the rule holds
in_window.remove(s[left])
left += 1
in_window.add(s[right])
best = max(best, right - left + 1)
return best
longest_no_repeat("streets") # 4| Approach | Time | Space | Why |
|---|---|---|---|
| Recompute every window | O(n · k) | O(1) | k additions for each of about n windows. |
| Fixed window | O(n) | O(1) | One addition and one subtraction per slide. |
| Growing and shrinking window | O(n) | O(n) | left and right each move at most n times. The set holds only the window's letters: at most n, and never more than 26 for a to z. |
The while inside the for looks like O(n²), but count the moves of left over the whole run. It only goes forward and never passes right, so it moves at most n times in total.
Common mistakes
Check yourself
Values [5, 1, 3, 6, 2], window size 3. The first window [5, 1, 3] sums to 9. What is the sum after one slide?
The growing-and-shrinking version has a while loop inside a for loop. What is its time complexity on a string of length n?
Which task is a poor fit for "grow right, shrink left while the sum is too big"?
What to practise next
A window answers questions about runs as it slides past them. When you need the sum of many different runs, in any order, or the numbers can be negative, Prefix Sums answers each one in O(1) after a single pass.