Sliding Window Pattern¶
Difficulty: Medium | Pattern Type: Array/String optimization
← DSA Overview | Next: Two Pointers →
Why This Pattern Exists¶
Problem: Find the maximum sum of any subarray of length K.
Brute force: For each starting position, sum the next K elements. - Time: O(N × K) - For N=10,000, K=1,000: 10 million operations
Insight: When we move the window forward by 1, we only need to: - Add one new element (the new right boundary) - Remove one old element (the old left boundary)
Instead of recalculating the sum each time (O(K)), we update it in O(1).
Result: O(N) instead of O(N × K).
Mental Model¶
Array: [3, 1, 2, 5, 8, 2, 6, 1, 4, 9] K=3
↑ ↑
L R
Window slides from left to right:
[3, 1, 2] → sum=6
[1, 2, 5] → sum=8
[2, 5, 8] → sum=15 ← maximum
[5, 8, 2] → sum=15
[8, 2, 6] → sum=16 ← new max
[2, 6, 1] → sum=9
[6, 1, 4] → sum=11
[1, 4, 9] → sum=14
Interactive Visualizer¶
Legend: Window Left pointer Right pointer
Two Variants¶
Fixed Window (Window size = K)¶
The window always has exactly K elements.
def max_sum_subarray(arr: list[int], k: int) -> int:
"""Maximum sum of any subarray of length k."""
if len(arr) < k:
return -1
# Initialize window with first k elements
window_sum = sum(arr[:k])
max_sum = window_sum
# Slide window: add one element, remove one element
for i in range(k, len(arr)):
window_sum += arr[i] # add new right element
window_sum -= arr[i - k] # remove old left element
max_sum = max(max_sum, window_sum)
return max_sum # O(n) time, O(1) space
# Test
print(max_sum_subarray([3, 1, 2, 5, 8, 2, 6, 1, 4, 9], 3)) # 16
Variable Window (Window shrinks/grows based on condition)¶
The window size changes to maintain a constraint.
The visualizer above is fixed-window max sum (not this variable-window problem). If a HUD ever showed Target=11, that was leftover state from a target-sum mode this page does not run.
def longest_subarray_with_sum_le_k(arr: list[int], k: int) -> int:
"""Longest contiguous subarray with sum ≤ k.
Assumes non-negative arr — shrinking from the left is only safe then
(a negative could make the sum smaller, so you might need a different algorithm)."""
left = 0
current_sum = 0
max_length = 0
for right in range(len(arr)):
current_sum += arr[right] # expand window
# Shrink window until constraint satisfied
while current_sum > k:
current_sum -= arr[left]
left += 1
# Window [left..right] satisfies constraint
max_length = max(max_length, right - left + 1)
return max_length # O(n) — each element enters/exits window once
Common Sliding Window Problems¶
1. Longest Substring Without Repeating Characters¶
def length_of_longest_substring(s: str) -> int:
"""
Pattern: Variable window — shrink when duplicate found
Clue: "longest substring" + "without repeating" → sliding window
"""
char_index = {} # char → last seen index
left = 0
max_len = 0
for right, char in enumerate(s):
# If char seen and within current window
if char in char_index and char_index[char] >= left:
left = char_index[char] + 1 # skip past duplicate
char_index[char] = right
max_len = max(max_len, right - left + 1)
return max_len
# "abcabcbb" → 3 ("abc")
# "pwwkew" → 3 ("wke")
# Time: O(n), Space: O(min(m,n)) where m=charset size
2. Minimum Window Substring¶
def min_window(s: str, t: str) -> str:
"""
Find the smallest window in s containing all chars of t.
Pattern: Variable window with character frequency tracking
"""
from collections import Counter
need = Counter(t)
have = {}
formed = 0 # how many chars satisfy the required frequency
required = len(need) # how many unique chars need to be satisfied
left = 0
min_len = float('inf')
result = ""
for right, char in enumerate(s):
have[char] = have.get(char, 0) + 1
if char in need and have[char] == need[char]:
formed += 1
# Try to shrink window
while formed == required:
if right - left + 1 < min_len:
min_len = right - left + 1
result = s[left:right + 1]
left_char = s[left]
have[left_char] -= 1
if left_char in need and have[left_char] < need[left_char]:
formed -= 1
left += 1
return result # O(s + t) time, O(s + t) space
3. Maximum Sum of K Consecutive Cards (Circular)¶
def max_score(card_points: list[int], k: int) -> int:
"""
Pick k cards from left or right end of array.
Insight: picking k from ends = leaving n-k in the middle.
Find minimum sum window of size n-k, answer = total - min_window.
"""
n = len(card_points)
window_size = n - k
total = sum(card_points)
if window_size == 0:
return total
window_sum = sum(card_points[:window_size])
min_window = window_sum
for i in range(window_size, n):
window_sum += card_points[i] - card_points[i - window_size]
min_window = min(min_window, window_sum)
return total - min_window
Pattern Recognition¶
When to use Sliding Window:
| Clue in problem | Window type |
|---|---|
| "subarray/substring of length K" | Fixed window |
| "longest subarray/substring satisfying X" | Variable window (expand right, shrink left) |
| "minimum window containing..." | Variable window with frequency map |
| "contiguous", "consecutive" | Sliding window |
| "at most K distinct", "no more than K" | Variable window |
Template — Variable Window:
def sliding_window_template(arr, constraint):
left = 0
state = initial_state() # e.g., Counter(), sum, set
result = initial_result()
for right in range(len(arr)):
update_state(arr[right], state) # add arr[right]
while not constraint(state): # window violates constraint
undo_state(arr[left], state) # remove arr[left]
left += 1
result = update_result(result, left, right)
return result
Complexity Analysis¶
| Problem | Time | Space | Key Insight |
|---|---|---|---|
| Max sum of k elements | O(n) | O(1) | Fixed window, running sum |
| Longest without repeating | O(n) | O(min(m,n)) | Variable window, hash map |
| Minimum window substring | O(s+t) | O(s+t) | Variable window, frequency count |
| Permutation in string | O(s+p) | O(p) | Fixed window, frequency match |
Edge Cases¶
# Always check:
# 1. Empty array/string
# 2. k > len(arr) (fixed window)
# 3. All same characters
# 4. Window never satisfies constraint (return 0 or "")
# 5. Answer is entire array
def robust_max_sum(arr, k):
if not arr or k > len(arr) or k <= 0:
return 0 # or raise ValueError
# ... rest of solution
Interview Follow-ups¶
- "What if the array is circular?" — Use the complement trick (find min window of n-k)
- "What if we need top-K windows, not just max?" — Use a heap of window sums
- "What if we can skip at most 1 element in the window?" — Extend variable window; try both including and excluding each element
- "What's the space complexity if elements are very large?" — Space depends on state tracking (hash map size), not element values
Key Takeaways¶
Remember
- Fixed window: O(N×K) brute force → O(N) by maintaining a running state
- Variable window: expand right to include, shrink left when constraint violated — each element enters/exits once → O(N)
- Key signal: "contiguous subarray/substring" + optimization goal → Sliding Window
- State tracking (sum, frequency map, set) is the variable that changes; update incrementally
- Template:
for right: add; while violated: remove left, left++; update result