Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4
1544 字
8 分钟
算法套路:固定滑动窗口——一行代码解决定长子串问题

写在前面#

滑动窗口是 LeetCode 中非常高频的算法技巧。它分为不定长定长两大类,其中固定滑动窗口(窗口大小恒为 kk)是最容易上手的一类——因为窗口大小不变,左右指针永远同步移动,逻辑极其简洁。

本文将用动画图解 + 通用模板 + 实战例题的方式,带你彻底搞懂固定滑动窗口。


一、核心思想:一句话概括#

窗口大小固定为 kk,每次右移一步:减去离开元素的贡献,加上新来元素的贡献,然后更新答案。

就这么简单。没有双指针收缩,没有 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 个黑块的最少涂色次数#

2379. 得到 K 个黑块的最少涂色次数

给你一个长度为 nn 的字符串 blocks,每个字符是 'W'(白色)或 'B'(黑色)。每次操作可以把一个白色块涂成黑色。求至少出现一次连续 kk 个黑色块的最少操作次数

  • 1 <= n <= 100
  • 1 <= k <= n

题目分析#

把题目翻译成大白话:

在一排黑白方块中,找一个长度为 kk 的连续区间,使得把里面的白色全涂黑的操作次数最少。

关键转化:一个窗口里有多少个 'W',就需要涂多少次。所以问题变成——

找一个长度为 kk 的窗口,使其中 'W' 的数量最少

这就是一个标准的固定滑动窗口问题!

动画图解#

blocks = "WBBWWBBWBW", k = 7 为例:

例题滑动窗口图解

滑动过程:

  1. 窗口 [0, 6]WBBWWBB → 3 个 W
  2. 窗口 [1, 7]BBWWBBW → 3 个 W
  3. 窗口 [2, 8]BWWBBWB → 3 个 W
  4. 窗口 [3, 9]WWBBWBW → 3 个 W

答案:min(3,3,3,3)=3\min(3, 3, 3, 3) = 3

代码实现#

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 ans

C++#

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;
}
};

复杂度分析#

维度复杂度说明
时间O(n)O(n)只遍历一遍字符串
空间O(1)O(1)只用了几个变量

五、更多练习题#

掌握了这个模板后,以下题目都可以直接套用:

题目维护变量更新方式
1456. 定长子串中元音的最大数目元音个数max
643. 子数组最大平均数 I窗口和max
2461. 长度为 K 子数组中的最大和不同元素和max
1343. 大小为 K 且平均值大于等于阈值的子数组数目窗口和计数
2090. 半径为 k 的子数组平均值窗口和数组

[!tip] 刷题建议 不要死记模板,理解”退出减、进入加、然后更新”这三步的含义。每道题的区别只在于:维护什么变量、退出/进入时怎么更新、答案取 max 还是 min 还是计数。


六、总结#

固定滑动窗口的核心就三句话:

  1. 初始化:用前 kk 个元素算出初始的 cnt
  2. 滑动:每次 cnt -= 退出的贡献; cnt += 进入的贡献;
  3. 更新:用 cnt 更新答案

没有 while,没有收缩,没有边界判断。掌握了这个套路,定长滑窗类题目就是秒杀

[!note] 参考与致谢

  • 灵茶山府的滑动窗口系列讲解
  • LeetCode 官方题解
算法套路:固定滑动窗口——一行代码解决定长子串问题
https://qiandaos.top/posts/sliding-window/
作者
千岛寒流
发布于
2025-04-23
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00