mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 13:12:44 +00:00
216 lines
6.1 KiB
Plaintext
216 lines
6.1 KiB
Plaintext
---
|
|
title: Sliding Window
|
|
description: 'Sliding window is a specialized two-pointer family where the pointers define a contiguous interval and you maintain information about that entire interval as it moves.'
|
|
hidden: true
|
|
---
|
|
|
|
Many problems have two moving boundaries. That alone does not make them sliding window problems.
|
|
|
|
A problem belongs to this family when:
|
|
|
|
- the answer depends on the subarray or substring between left and right
|
|
- you can update the window state incrementally as elements enter or leave
|
|
- you maintain a condition that tells you whether the window is valid
|
|
|
|
:::tip
|
|
The rule for all three: every element enters once and leaves once, so the whole thing runs in O(n).
|
|
:::
|
|
|
|
<Accordion>
|
|
<AccordionItem title="What is an invariant?">
|
|
An invariant is a statement that stays true before and after every iteration. In a sliding window, that statement usually describes the window itself, such as “the window is valid,” “it has no duplicates,” or “its sum is at most the target.” It can also describe a helper structure, such as “the frequency map matches the characters currently inside the window.”
|
|
</AccordionItem>
|
|
<AccordionItem title="Why invariants matter?">
|
|
Without an invariant, shrinking the window is just a guess. You no longer know when the window is valid, when to update the answer, or why the left pointer should stop. The invariant is what makes the algorithm correct.
|
|
</AccordionItem>
|
|
<AccordionItem title="How sliding window differs from other two-pointer families?">
|
|
Sliding window is not the same as every other two-pointer pattern. In Container With Most Water, the interval is only the remaining search space. In sliding window, the interval itself is the thing you care about. In Remove Duplicates, the left side is already the finished answer prefix. In sliding window, both pointers describe a live window that is still being checked.
|
|
</AccordionItem>
|
|
</Accordion>
|
|
|
|
|
|
## 1. Fixed-size windows
|
|
|
|
:::tip
|
|
The window size never changes. The main task is updating the window state efficiently when one element enters and one leaves.
|
|
:::
|
|
|
|
|
|
<Frame caption="Sliding Window Fixed" hint="">
|
|
|
|

|
|
|
|
</Frame>
|
|
|
|
|
|
```python
|
|
def fixed_window(nums, k):
|
|
window_sum = 0
|
|
best = float('-inf')
|
|
|
|
for right in range(len(nums)):
|
|
window_sum += nums[right] # add the new element
|
|
|
|
if right >= k - 1: # window is full
|
|
best = max(best, window_sum) # record answer
|
|
window_sum -= nums[right - k + 1] # remove the oldest element
|
|
|
|
return best
|
|
```
|
|
|
|
<Steps>
|
|
<Step title="">
|
|
Add `nums[right]` to the window.
|
|
</Step>
|
|
<Step title="">
|
|
If the window has `k` elements, record the answer.
|
|
</Step>
|
|
<Step title="">
|
|
Remove the leftmost element so the window stays at size `k`.
|
|
</Step>
|
|
</Steps>
|
|
|
|
|
|
## 2. Flexible-size windows with a validity rule
|
|
|
|
:::tip
|
|
Here the window expands until invalid, then shrinks until valid again.
|
|
:::
|
|
|
|
<Frame caption="Sliding Window Flexible" hint="">
|
|
|
|

|
|
|
|
</Frame>
|
|
|
|
```python
|
|
def flexible_window(nums, target):
|
|
left = 0
|
|
window_sum = 0
|
|
best = 0
|
|
|
|
for right in range(len(nums)):
|
|
window_sum += nums[right] # grow the window
|
|
|
|
while window_sum > target: # window is invalid
|
|
window_sum -= nums[left] # shrink from the left
|
|
left += 1
|
|
|
|
best = max(best, right - left + 1) # window is valid, record answer
|
|
|
|
return best
|
|
```
|
|
|
|
<Steps>
|
|
<Step title="">
|
|
Add `nums[right]` to the window.
|
|
</Step>
|
|
<Step title="">
|
|
While the window is invalid, remove `nums[left]` and move `left` up.
|
|
</Step>
|
|
<Step title="">
|
|
The window is now valid. Record its size.
|
|
</Step>
|
|
</Steps>
|
|
|
|
---
|
|
|
|
For "shortest window" problems, swap the logic: use `while` for the *valid* case, record the answer inside the loop, then shrink.
|
|
|
|
```python
|
|
def shortest_valid_window(nums, target):
|
|
left = 0
|
|
window_sum = 0
|
|
best = float('inf')
|
|
|
|
for right in range(len(nums)):
|
|
window_sum += nums[right]
|
|
|
|
while window_sum >= target: # window is valid
|
|
best = min(best, right - left + 1) # record, then try to shrink
|
|
window_sum -= nums[left]
|
|
left += 1
|
|
|
|
return best if best != float('inf') else 0
|
|
|
|
```
|
|
|
|
## 3. Frequency-tracking windows
|
|
|
|
:::tip
|
|
The difficulty comes from keeping the bookkeeping correct as characters enter and leave.
|
|
:::
|
|
|
|
|
|
<Frame caption="Frequency-tracking windows" hint="">
|
|
|
|

|
|
|
|
</Frame>
|
|
|
|
```python
|
|
from collections import defaultdict
|
|
|
|
def frequency_window(s):
|
|
left = 0
|
|
counts = defaultdict(int)
|
|
best = 0
|
|
|
|
for right in range(len(s)):
|
|
counts[s[right]] += 1 # character enters
|
|
|
|
while counts[s[right]] > 1: # rule: no repeats
|
|
counts[s[left]] -= 1 # character leaves
|
|
left += 1
|
|
|
|
best = max(best, right - left + 1)
|
|
|
|
return best
|
|
```
|
|
|
|
<Steps>
|
|
<Step title="">
|
|
Add `s[right]` to the window.
|
|
</Step>
|
|
<Step title="">
|
|
While the window has more than one character, remove `s[left]` and move `left` up.
|
|
</Step>
|
|
<Step title="">
|
|
The window is now valid. Record its size.
|
|
</Step>
|
|
</Steps>
|
|
|
|
---
|
|
|
|
For anagram or "minimum window substring" problems, you also keep a target count. Track how many characters currently match the target with one integer (`matched`). Update it only when a count crosses the target value. This keeps each step `O(1)`.
|
|
|
|
```python
|
|
def anagram_window(s, p):
|
|
need = defaultdict(int)
|
|
for c in p:
|
|
need[c] += 1
|
|
|
|
counts = defaultdict(int)
|
|
matched = 0 # how many chars have counts[c] == need[c]
|
|
result = []
|
|
k = len(p)
|
|
|
|
for right in range(len(s)):
|
|
c = s[right]
|
|
counts[c] += 1
|
|
if counts[c] == need[c]:
|
|
matched += 1
|
|
|
|
if right >= k: # window too big, remove left
|
|
d = s[right - k]
|
|
if counts[d] == need[d]:
|
|
matched -= 1
|
|
counts[d] -= 1
|
|
|
|
if matched == len(need):
|
|
result.append(right - k + 1)
|
|
|
|
return result
|
|
```
|
|
|