滑动窗口(Sliding Window) 是算法中非常常见且高频出现的一类技巧,尤其在处理连续子数组/子串相关问题时,几乎是“必备武器”。
它最核心的思想是:
通过维护一个固定长度或动态长度的窗口,在数组/字符串上只向前滑动(不回溯),从而把暴力 O(n²) 降到 O(n) 或 O(n log n)。
滑动窗口的两种主要形态(Python实现角度)
| 类型 | 窗口长度 | 典型代表题 | 时间复杂度 | 核心维护什么? |
|---|---|---|---|---|
| 固定长度窗口 | 窗口大小固定 | 长度为 k 的最大/最小子数组和 | O(n) | 窗口内元素和/计数/最大值等 |
| 动态长度窗口 | 窗口大小可变 | 最长无重复子串、最小覆盖子串 | O(n) | 满足/不满足条件的边界移动 |
下面分别给出两种最经典的写法模板 + 代表题目代码。
一、固定长度滑动窗口模板
def fixed_window(arr, k):
if len(arr) < k:
return None # 或根据题目要求返回
# 初始化第一个窗口 [0...k-1]
window_sum = sum(arr[:k]) # 窗口内元素和(最常见)
# window_max = max(arr[:k]) # 如果求最大值
# window_count = Counter(arr[:k]) # 如果需要计数
result = window_sum # 或者其他统计量
# 窗口向右滑动
for i in range(k, len(arr)):
# 移除左边界
window_sum -= arr[i - k]
# 加入右边界
window_sum += arr[i]
# 更新答案(根据题目要求)
result = max(result, window_sum) # 例如求最大和
return result
经典例题:
力扣 643. 子数组最大平均数 I(窗口大小为 k 的最大平均值)
def findMaxAverage(nums: List[int], k: int) -> float:
if len(nums) < k:
return 0.0
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum = window_sum - nums[i - k] + nums[i]
max_sum = max(max_sum, window_sum)
return max_sum / k
二、动态长度滑动窗口模板(最经典写法)
def dynamic_window(s: str) -> int:
# 记录窗口内字符出现的次数(或状态)
window = {} # 哈希表 / Counter
left = 0
ans = 0 # 记录最优答案
for right in range(len(s)):
# 1. 把右边界字符加入窗口
c = s[right]
window[c] = window.get(c, 0) + 1
# 2. 判断是否需要收缩左边界(while 循环)
while window.get(c, 0) > 1: # 条件根据题目变化
# 移除左边界字符
d = s[left]
window[d] -= 1
if window[d] == 0:
window.pop(d)
left += 1
# 3. 此时 [left, right] 是合法窗口
ans = max(ans, right - left + 1)
return ans
最经典例题:
力扣 3. 无重复字符的最长子串
def lengthOfLongestSubstring(s: str) -> int:
if not s:
return 0
window = {} # char -> last index
left = 0
max_len = 0
for right in range(len(s)):
if s[right] in window and window[s[right]] >= left:
# 发现重复,移动左指针到上一次出现位置的右边
left = window[s[right]] + 1
# 更新该字符最新出现位置
window[s[right]] = right
max_len = max(max_len, right - left + 1)
return max_len
三、滑动窗口常见变形 & 模板总结(背下来很有用)
| 题目类型 | 窗口移动条件(while 里写什么) | 记录什么答案 | 推荐写法 |
|---|---|---|---|
| 最长无重复子串 | while s[right] in window and window[s[right]] >= left | right – left + 1 | 记录最后出现位置 |
| 最小覆盖子串 | while 窗口已包含所有字符 | 记录最小长度窗口 | 记录有效次数 |
| 长度为 k 的最大和/最小和 | 无需 while,固定移动 k 长度 | 维护窗口和/最大值 | 前缀和或直接累加 |
| 至多 k 个不同字符的最长子串 | while len(window) > k | right – left + 1 | Counter 或 set |
| 包含所有 T 中字符的最短子串 | while 有效字符数 == len(t) | 更新最小长度 | need, have 计数器 |
四、练习推荐顺序(强烈建议按这个顺序刷)
-
- 长度最小的子数组(和 ≥ target)
- 3. 无重复字符的最长子串
- 最小覆盖子串
- 找到字符串中所有字母异位词
- 字符串的排列
- 替换后的最长重复字符
- 最大连续1的个数 III(至多翻转 K 个 0)
- 滑动窗口最大值(使用双端队列)
五、快速记忆口诀
左指针不动则右移,右移必加入元素
条件不满足则左移,左移必移除元素
每次右指针移动都尝试更新答案
while 收缩左边界,直到条件重新满足
如果你现在想看某一道具体题目的完整代码(比如 76、239、424 等),或者想看双指针 + 滑动窗口的混合写法,可以直接告诉我题号或关键词,我给你写最清晰的 Python 版本。
你目前最想看哪一类滑动窗口的代码?