Sliding Window
Maintain a contiguous window over the input and slide it forward, updating state incrementally instead of recalculating from scratch. The pattern that turns O(n·k) brute force into one clean O(n) pass.
Stop recomputing what you already know
Brute force re-sums every subarray: O(n · k). The window keeps a running
state and does O(1) work per step — subtract the element leaving on the left,
add the element arriving on the right.
Fixed vs. dynamic windows
Type AFixed size
The window has a predetermined, constant size k. Slide one step at a time;
record an answer whenever the window is full.
Type BDynamic size
The window expands on the right and contracts on the left based on a condition. Used for "longest substring such that…" and "shortest subarray such that…" problems.
When to reach for it
- The problem asks about a contiguous subarray or substring.
- You see "longest / shortest / max / min / count" + "substring / subarray" + a constraint.
- A brute-force answer would re-examine overlapping ranges.
- The constraint is monotonic: growing the window only makes it "more" (bigger sum, more distinct chars) and shrinking makes it "less."
The three templates to memorize
T1Fixed window
def max_sum_fixed(nums, k):
window_sum = 0
best = float("-inf")
for right in range(len(nums)):
window_sum += nums[right] # 1. add the arriving element
if right >= k: # 2. window too big -> evict left
window_sum -= nums[right - k]
if right >= k - 1: # 3. window full -> record
best = max(best, window_sum)
return best
T2Dynamic — longest valid window
def longest_no_repeat(s):
seen = {} # char -> latest index
left = 0
best = 0
for right, ch in enumerate(s):
if ch in seen and seen[ch] >= left:
left = seen[ch] + 1 # jump left past the duplicate
seen[ch] = right
best = max(best, right - left + 1) # window is ALWAYS valid here
return best
T3Dynamic — shortest valid window
def min_subarray_len(target, nums):
left = 0
window_sum = 0
best = float("inf")
for right in range(len(nums)):
window_sum += nums[right]
while window_sum >= target: # shrink WHILE valid
best = min(best, right - left + 1)
window_sum -= nums[left]
left += 1
return best if best != float("inf") else 0
left never moves backward.
Total: at most 2n pointer moves. The nested loop is amortized O(1) per step.
Where windows go wrong
len(char_count) stays wrong until you del zero-count keys.
# WRONG: leaves ghost keys, len(char_count) lies
char_count[leaving] -= 1
# RIGHT: clean up zero-count keys
char_count[leaving] -= 1
if char_count[leaving] == 0:
del char_count[leaving]
right == k - 1, not k.
And window size is right - left + 1, not right - left.
Counter objects each iteration is O(k). Use the
have / need integer trick (LC 76, 438) for O(1) checks.
Practice ladder
In this order — easiest first. Tick each one as you solve it, and re-solve anything that took more than 25 minutes two days later.