写在前面
滑动窗口是 LeetCode 中非常高频的算法技巧。它分为不定长和定长两大类,其中固定滑动窗口(窗口大小恒为 )是最容易上手的一类——因为窗口大小不变,左右指针永远同步移动,逻辑极其简洁。
本文将用动画图解 + 通用模板 + 实战例题的方式,带你彻底搞懂固定滑动窗口。
一、核心思想:一句话概括
窗口大小固定为 ,每次右移一步:减去离开元素的贡献,加上新来元素的贡献,然后更新答案。
就这么简单。没有双指针收缩,没有 while 循环——只有 O(1) 的增减操作。
直觉理解
想象你拿着一个固定长度的尺子在一条带子上滑动:
- 尺子每往右挪一格,左边掉出去一个元素,右边进来一个元素
- 你只需要关心这两个变化对”统计量”的影响
- 不需要重新扫描整个窗口

二、动画演示
下面是一个可交互的动画,展示了 LeetCode 1456. 定长子串中元音的最大数目 的滑动过程。点击「下一步」逐步观察,或点击「自动播放」看完整动画。
三、通用解题模板
固定滑动窗口的代码可以浓缩为一个非常精简的模板。核心只有三步:
模板代码(Python)
def fixed_window(s: str, k: int) -> int: n = len(s) # ========== 第一步:初始化 ========== # 用前 k 个元素计算初始的维护变量 cnt cnt = 0 for i in range(k): cnt += ... # 根据题意,计算 s[i] 的贡献
ans = cnt # 用初始窗口更新答案
# ========== 第二步:滑动窗口 ========== # 枚举窗口右端点 i(从 k 到 n-1) for i in range(k, n): # ① 移除 s[i - k] 的贡献(左边掉出去的) cnt -= ... # ② 加入 s[i] 的贡献(右边新进来的) cnt += ... # ③ 更新答案 ans = max(ans, cnt) # 或 min / 计数等
return ans模板代码(C++)
int fixedWindow(string& s, int k) { int n = s.size(); // ========== 第一步:初始化 ========== int cnt = 0; for (int i = 0; i < k; i++) { cnt += ...; // s[i] 的贡献 } int ans = cnt;
// ========== 第二步:滑动窗口 ========== for (int i = k; i < n; i++) { cnt -= ...; // 移除 s[i - k] cnt += ...; // 加入 s[i] ans = max(ans, cnt); } return ans;}关键细节
[!warning] 循环不变量 在每次更新
ans之前,cnt必须恰好表示当前窗口[i-k+1, i]的状态。顺序是:先更新cnt,再更新ans,不能反。
[!note] 为什么不需要 while 循环? 不定长滑动窗口需要 while 循环来收缩左边界,但固定窗口大小不变,左边界永远是
i - k,所以只需要一次减法。
四、实战:LeetCode 2379. 得到 K 个黑块的最少涂色次数
给你一个长度为 的字符串
blocks,每个字符是'W'(白色)或'B'(黑色)。每次操作可以把一个白色块涂成黑色。求至少出现一次连续 个黑色块的最少操作次数。
1 <= n <= 1001 <= k <= n
题目分析
把题目翻译成大白话:
在一排黑白方块中,找一个长度为 的连续区间,使得把里面的白色全涂黑的操作次数最少。
关键转化:一个窗口里有多少个 'W',就需要涂多少次。所以问题变成——
找一个长度为 的窗口,使其中
'W'的数量最少。
这就是一个标准的固定滑动窗口问题!
动画图解
以 blocks = "WBBWWBBWBW", k = 7 为例:

滑动过程:
- 窗口
[0, 6]:WBBWWBB→ 3 个 W - 窗口
[1, 7]:BBWWBBW→ 3 个 W - 窗口
[2, 8]:BWWBBWB→ 3 个 W - 窗口
[3, 9]:WWBBWBW→ 3 个 W
答案:
代码实现
Python
class Solution: def minimumRecolors(self, blocks: str, k: int) -> int: n = len(blocks) # 第一步:初始化,统计前 k 个字符中 'W' 的数量 cnt = blocks[:k].count('W') ans = cnt
# 第二步:滑动窗口 for i in range(k, n): # ① 左边掉出去的字符 if blocks[i - k] == 'W': cnt -= 1 # ② 右边新进来的字符 if blocks[i] == 'W': cnt += 1 # ③ 更新答案 ans = min(ans, cnt)
return ansC++
class Solution {public: int minimumRecolors(string blocks, int k) { int n = blocks.size(); // 第一步:初始化 int cnt = 0; for (int i = 0; i < k; i++) { if (blocks[i] == 'W') cnt++; } int ans = cnt;
// 第二步:滑动窗口 for (int i = k; i < n; i++) { if (blocks[i - k] == 'W') cnt--; // 退出 if (blocks[i] == 'W') cnt++; // 进入 ans = min(ans, cnt); } return ans; }};复杂度分析
| 维度 | 复杂度 | 说明 |
|---|---|---|
| 时间 | 只遍历一遍字符串 | |
| 空间 | 只用了几个变量 |
五、更多练习题
掌握了这个模板后,以下题目都可以直接套用:
| 题目 | 维护变量 | 更新方式 |
|---|---|---|
| 1456. 定长子串中元音的最大数目 | 元音个数 | max |
| 643. 子数组最大平均数 I | 窗口和 | max |
| 2461. 长度为 K 子数组中的最大和 | 不同元素和 | max |
| 1343. 大小为 K 且平均值大于等于阈值的子数组数目 | 窗口和 | 计数 |
| 2090. 半径为 k 的子数组平均值 | 窗口和 | 数组 |
[!tip] 刷题建议 不要死记模板,理解”退出减、进入加、然后更新”这三步的含义。每道题的区别只在于:维护什么变量、退出/进入时怎么更新、答案取 max 还是 min 还是计数。
六、总结
固定滑动窗口的核心就三句话:
- 初始化:用前 个元素算出初始的
cnt - 滑动:每次
cnt -= 退出的贡献; cnt += 进入的贡献; - 更新:用
cnt更新答案
没有 while,没有收缩,没有边界判断。掌握了这个套路,定长滑窗类题目就是秒杀。
[!note] 参考与致谢
- 灵茶山府的滑动窗口系列讲解
- LeetCode 官方题解
部分信息可能已经过时





