feat(patterns): tutorial system
This commit is contained in:
194
backend/data/patterns/sliding-window.yaml
Normal file
194
backend/data/patterns/sliding-window.yaml
Normal file
@@ -0,0 +1,194 @@
|
||||
name: Sliding Window
|
||||
slug: sliding-window
|
||||
difficulty_level: 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
|
||||
Reference in New Issue
Block a user