滑动窗口(Sliding Window)是算法面试和实际开发中最常见的技巧之一,尤其在数组、字符串问题中。它能把暴力 O(n²) 降到 O(n)。
下面从最基础开始,用 Python 一步步讲解,适合初学者到中级开发者复习。
1. 滑动窗口核心思想(一句话记住)
用两个指针(left 和 right)维护一个连续窗口,根据条件动态扩大或收缩窗口,只遍历一次数组。
两大类:
- 固定长度窗口:窗口大小固定(如 k),只滑动
- 可变长度窗口:窗口大小动态变化,根据条件收缩 left
2. 固定长度滑动窗口模板(最简单)
def fixed_window(nums: list[int], k: int) -> int:
if len(nums) < k:
return 0 # 或其他边界值
# 初始化第一个窗口 [0, k-1]
window_sum = sum(nums[:k]) # 当前窗口和
max_sum = window_sum # 记录最大值(或最小、计数等)
# 从第 k 个元素开始滑动
for right in range(k, len(nums)):
# 窗口右移:减去最左边 + 加上新右边
window_sum = window_sum - nums[right - k] + nums[right]
max_sum = max(max_sum, window_sum)
return max_sum
经典例题:LeetCode 643. 子数组最大平均数 I(固定窗口 k)
def findMaxAverage(nums: list[int], k: int) -> float:
current = sum(nums[:k])
max_sum = current
for i in range(k, len(nums)):
current = current - nums[i - k] + nums[i]
max_sum = max(max_sum, current)
return max_sum / k
3. 可变长度滑动窗口模板(最常用)
def variable_window(s: str) -> int:
# 记录窗口内某种状态(如字符出现次数)
count = {} # 哈希表 / Counter
left = 0
max_len = 0
for right in range(len(s)):
# 1. 右指针进窗口
count[s[right]] = count.get(s[right], 0) + 1
# 2. 判断是否非法 → while 收缩 left
while count.get(s[right], 0) > 1: # 条件:不允许重复字符
count[s[left]] -= 1
if count[s[left]] == 0:
del count[s[left]]
left += 1
# 3. 此时窗口 [left, right] 合法,更新答案
max_len = max(max_len, right - left + 1)
return max_len
最经典例题:LeetCode 3. 无重复字符的最长子串
def lengthOfLongestSubstring(s: str) -> int:
if not s:
return 0
char_index = {} # 记录字符最后出现的位置(更高效写法)
left = 0
max_len = 0
for right in range(len(s)):
if s[right] in char_index and char_index[s[right]] >= left:
# 发现重复,且重复位置在当前窗口内 → 移动 left
left = char_index[s[right]] + 1
char_index[s[right]] = right
max_len = max(max_len, right - left + 1)
return max_len
4. 常见滑动窗口变种模板对比(一图秒懂)
| 类型 | 窗口大小 | 移动方式 | 典型题目(LeetCode) | 时间复杂度 |
|---|---|---|---|---|
| 固定窗口 | 固定 k | 每次只移动 right 和 left=k步 | 643、209(变形)、1004 | O(n) |
| 可变窗口 – 最长 | 尽量大 | right 扩张,left 收缩到合法 | 3、424、567、76(最小覆盖子串) | O(n) |
| 可变窗口 – 最短 | 尽量小 | right 扩张到合法,left 收缩 | 209(长度最小的子数组)、76(最小覆盖子串) | O(n) |
| 最多允许 k 个不同 | 动态 | 记录不同字符数 | 340、159、904 | O(n) |
| 最多 k 次替换 | 动态 | 记录需要替换的次数 | 424(最长重复字符替换)、1004 | O(n) |
| 滑动窗口最大值 | 固定 k | 单调队列维护最大值 | 239 | O(n) |
5. 初学者练习推荐顺序(强烈建议按顺序刷)
- Maximum Average Subarray I(固定窗口入门)
- Minimum Size Subarray Sum(最短子数组)
- 3. Longest Substring Without Repeating Characters(最经典)
- Permutation in String(固定长度 + 计数)
- Longest Repeating Character Replacement(k 次替换)
- Minimum Window Substring(最小覆盖子串,难度较高)
- Sliding Window Maximum(单调队列)
6. 写滑动窗口时最容易犯的 5 个错(避坑指南)
- 忘记 left 初始化为 0
- while 条件写成 if(导致窗口不完全收缩)
- 收缩 left 后没有更新状态(如 count[left] -=1)
- right – left + 1 写成 right – left(少 1)
- 边界情况:空字符串、k > len(nums)、所有元素相同
一句话总结:
滑动窗口 = 两个指针 + 一段状态 + while 收缩 + 更新答案
如果你现在能手写上面 LeetCode 3 和 643 的代码,滑动窗口就基本入门了。
想针对某个具体题目(如 239、76、1004)要详细代码 + 思路吗?或者想看某个变种的完整模板?直接告诉我~