Files
codetutor/backend/data/patterns/sliding-window.yaml

323 lines
10 KiB
YAML

name: Sliding Window
slug: sliding-window
difficulty_level: 2
pattern_type: algorithm
display_order: 2
description: >
Maintain a window of elements that slides through the data, tracking a
constraint or computing aggregates. This transforms O(n*k) brute force into
O(n) by incrementally updating the window instead of recalculating from scratch.
when_to_use: |
- Finding subarrays/substrings with specific properties
- Maximum/minimum sum of fixed-size windows
- Longest substring with at most K distinct characters
- Problems mentioning "contiguous" elements
metaphor: |
Imagine looking at a landscape through a train window. As the train moves
forward, the scenery at the back of your view disappears while new scenery
appears at the front. You don't need to memorize the entire journey—just
keep track of what's currently visible through your window.
Another analogy: a cashier's sliding tray at a bank. As new items are added
to one end, old items fall off the other. You only count what's on the tray
at any moment.
core_concept: |
The **sliding window** technique avoids redundant computation by maintaining
state as the window moves. Instead of recalculating the entire window each
time, you *add* what enters and *remove* what leaves.
There are two main types:
1. **Fixed-size window**: Window size is constant (e.g., "find max sum of k elements")
2. **Variable-size window**: Window expands and contracts based on constraints
(e.g., "longest substring with at most 2 distinct characters")
The key insight is that consecutive windows share most of their elements.
Only the edges change, so only update those.
visualization: |
**Example: Maximum sum of 3 consecutive elements**
```
Array: [2, 1, 5, 1, 3, 2] Window size: 3
Window 1: [2, 1, 5] = 8 (calculate full sum)
└─────┘
Window 2: [1, 5, 1] = 8-2+1 = 7 (remove 2, add 1)
└─────┘
Window 3: [5, 1, 3] = 7-1+3 = 9 (remove 1, add 3) ← Maximum!
└─────┘
Window 4: [1, 3, 2] = 9-5+2 = 6 (remove 5, add 2)
└─────┘
```
**Variable window: Longest substring with at most 2 distinct chars**
```
String: "eceba"
"e" → 1 distinct, expand → length 1
"ec" → 2 distinct, expand → length 2
"ece" → 2 distinct, expand → length 3 ← Answer!
"eceb" → 3 distinct, shrink from left
"ceb" → 3 distinct, shrink from left
"eb" → 2 distinct, expand → length 2
"eba" → 3 distinct, shrink...
```
code_template: |
def fixed_window(arr: list, k: int) -> int:
"""Fixed-size sliding window."""
n = len(arr)
if n < k:
return 0
# Calculate initial window
window_sum = sum(arr[:k])
max_sum = window_sum
# Slide the window
for i in range(k, n):
window_sum += arr[i] - arr[i - k] # Add new, remove old
max_sum = max(max_sum, window_sum)
return max_sum
def variable_window(s: str, k: int) -> int:
"""Variable-size sliding window."""
char_count = {}
left = 0
max_length = 0
for right in range(len(s)):
# Expand: add character at right
char_count[s[right]] = char_count.get(s[right], 0) + 1
# Contract: shrink from left if constraint violated
while len(char_count) > k:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
# Update answer
max_length = max(max_length, right - left + 1)
return max_length
recognition_signals:
- "contiguous subarray"
- "substring"
- "maximum sum of k elements"
- "window"
- "consecutive"
- "at most k distinct"
- "minimum window"
- "longest substring"
- "sliding"
common_mistakes:
- title: Forgetting to handle window smaller than required
description: |
When array length is less than window size k, trying to create a window
causes index errors or incorrect results.
fix: |
Add an early check:
```python
if len(arr) < k:
return 0 # or appropriate default
```
- title: Off-by-one in variable window
description: |
When calculating window length, using `right - left` instead of
`right - left + 1` gives length off by one.
fix: |
Window length is always `right - left + 1` (inclusive on both ends).
- title: Not cleaning up empty entries in hash map
description: |
When shrinking a variable window, decrementing a counter to 0 but not
removing the key causes the distinct count to be wrong.
fix: |
Always delete keys when count reaches 0:
```python
if char_count[s[left]] == 0:
del char_count[s[left]]
```
- title: Updating answer at wrong time
description: |
For "minimum" problems, updating the answer inside the while loop
captures invalid states. For "maximum" problems, updating only inside
the while loop misses valid states.
fix: |
For maximum problems, update after expanding. For minimum problems,
update when the constraint is first satisfied (inside the while loop).
variations:
- name: Fixed-size window
description: |
Window size stays constant throughout. Simple slide operation: add one
element, remove one element.
example: "Maximum Sum Subarray of Size K, Find All Anagrams"
- name: Variable-size (shrinkable)
description: |
Window expands freely but contracts when constraints are violated.
Uses a while loop to shrink until valid.
example: "Longest Substring Without Repeating, Minimum Window Substring"
- name: Two-pointer variant
description: |
Some problems use two pointers that feel like sliding window but track
different metrics. The mechanics are similar.
example: "Container With Most Water, Trapping Rain Water"
- name: Caterpillar method
description: |
Another name for the variable sliding window, emphasizing how the window
stretches and contracts like a caterpillar moving.
example: "Common in competitive programming contexts"
related_patterns:
- two-pointers
- prefix-sum
prerequisite_patterns:
- two-pointers
visualization_examples:
- id: max-sum-subarray
title: Maximum Sum Subarray of Size K
input:
nums: [2, 1, 5, 1, 3, 2]
k: 3
code: |
def max_sum_subarray(nums, k):
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum += nums[i] - nums[i - k]
max_sum = max(max_sum, window_sum)
return max_sum
steps:
- id: step-1
description: "Initialize window with first k=3 elements: [2, 1, 5]. Sum = 8"
structures:
nums:
type: array
values:
- { value: 2, state: active, annotations: ["window"] }
- { value: 1, state: active, annotations: ["window"] }
- { value: 5, state: active, annotations: ["window"] }
- { value: 1, state: default }
- { value: 3, state: default }
- { value: 2, state: default }
pointers:
left: 0
right: 2
variables:
window_sum: 8
max_sum: 8
codeHighlight:
startLine: 2
endLine: 3
- id: step-2
description: "Slide window: remove 2, add 1. New window [1, 5, 1]. Sum = 7"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: active, annotations: ["window"] }
- { value: 5, state: active, annotations: ["window"] }
- { value: 1, state: active, annotations: ["window"] }
- { value: 3, state: default }
- { value: 2, state: default }
pointers:
left: 1
right: 3
variables:
window_sum: 7
max_sum: 8
i: 3
codeHighlight:
startLine: 5
endLine: 7
- id: step-3
description: "Slide window: remove 1, add 3. New window [5, 1, 3]. Sum = 9. New max!"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: found, annotations: ["window"] }
- { value: 1, state: found, annotations: ["window"] }
- { value: 3, state: found, annotations: ["window"] }
- { value: 2, state: default }
pointers:
left: 2
right: 4
variables:
window_sum: 9
max_sum: 9
i: 4
codeHighlight:
startLine: 5
endLine: 7
- id: step-4
description: "Slide window: remove 5, add 2. New window [1, 3, 2]. Sum = 6"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: visited }
- { value: 1, state: active, annotations: ["window"] }
- { value: 3, state: active, annotations: ["window"] }
- { value: 2, state: active, annotations: ["window"] }
pointers:
left: 3
right: 5
variables:
window_sum: 6
max_sum: 9
i: 5
codeHighlight:
startLine: 5
endLine: 7
- id: step-5
description: "Loop complete. Maximum sum found is 9 (window [5, 1, 3])"
structures:
nums:
type: array
values:
- { value: 2, state: visited }
- { value: 1, state: visited }
- { value: 5, state: found }
- { value: 1, state: found }
- { value: 3, state: found }
- { value: 2, state: visited }
variables:
max_sum: 9
codeHighlight:
startLine: 9
endLine: 9