滑动窗口
两个指针 l、r,r 一路往右扩;窗口”违法”了就 l 往右收,合法了就更新答案。两个指针各自最多走 N 步,
直觉 — 两指针单调推进
把暴力枚举所有子数组想象成一个二维网格 r 永远只增,l 也永远只增。两个指针加起来最多
为什么 l 不会回退? 因为 r 往右一步只会让窗口”更违法”(更多元素、更大和、更多重复),已经退到合法的 l 没有理由再往左跑回去 — 那只会让窗口又变违法。
什么时候能用滑动窗口
关键判定: 单调性。 必须满足:
- 窗口扩大 → 条件朝一个方向变化(更违法 / 更合法)
- 窗口缩小 → 朝相反方向变化
比如”子数组和
判错就死: 看到题先问一句”扩大窗口,条件是不是只朝一个方向走?”是,才能滑。
动画 — 两种模板并排看
下面动画分两部分。上半演示「最长无重复子串」("abcabcbb"),看 r 扩到出现重复时 l 怎么收,ans 在 while 循环外更新。下半左右对比同一套指针逻辑下两种 ans 更新位置: 左边「最长合法」(while 违法时收,while 外更新),右边「最短覆盖」(while 合法时记录,while 内更新)。
重点看两件事: (1) l 从来不回退;(2) ans 更新的位置决定了你算的是最长还是最短。
两种模板 — 最长合法 vs 最短覆盖
模板 A: 最长合法窗口(while 违法时收)
1 | |
直觉: 出 while 时窗口刚刚回到合法,这是以 right 为右端的最大合法窗口,记下来。
模板 B: 最短覆盖窗口(while 合法时记)
1 | |
直觉: 进 while 说明已经合法,在收缩过程中每一步都是一个合法窗口,贪心地取最短。一旦 l 收到再收就违法,跳出去等 r 继续扩。
怎么对应到题目
| 题目特征 | 用哪个模板 | ans 更新位置 |
|---|---|---|
| 找最长 X 子数组 | A: 最长合法 | while 外 |
| 找最短 X 子数组 | B: 最短覆盖 | while 内 |
| 数有多少子数组 X | A 的变体: ans += right - left + 1 |
while 外 |
第三种是高频小变招: 出 while 后 [left..right] 是以 right 结尾的最大合法窗口,所以以 right 结尾的合法子数组共有 right - left + 1 个。
经典题速过
LC 3. 无重复字符的最长子串 (模板 A)
1 | |
小优化: 违法当且仅当新进来的 c 重复,所以 while window[c] > 1 比扫整个 window 快。
LC 209. 长度最小的子数组 (模板 B)
1 | |
LC 76. 最小覆盖子串 (模板 B + 计数器)
1 | |
关键: 用 have 记录”已满足字符种类数”,避免每次扫整个 Counter 判合法,这样合法性判断真的是
“恰好 K” = “至多 K” - “至多 K-1” (LC 992)
恰好 K 没单调性 → 转成两个有单调性的”至多”:
1 | |
不写错的三条铁律
- 进出对称:
right进窗口做的操作,left出窗口做对应逆操作。Counter 别忘了在归零时del,否则len(cnt)不对。 left只在 while 里动: 在外面动left,均摊分析直接崩。ans位置 = 题目意图: while 外 → 最长 / 计数;while 内 → 最短。
记忆法
一句话: r 扩到违法 → l 缩到合法。
判断三连问:
- 扩窗朝一个方向变(单调)? 不是 → 不能滑(或转化)。
- 找最长还是最短? 最长 → while 违法时收,while 外更新;最短 → while 合法时记,while 内更新。
- 进出对称写好了吗? Counter 归零删,sum 加完减。
看到”连续子数组/子串 + 最长/最短/计数”,条件反射写两指针外加一个 while。错不了。
思考题
为什么左指针只向右移动,不会回退? 这保证了什么复杂度?
r 右移后窗口只可能”更违法”,已经退到合法的 l 没理由再往左走 — 那只会让窗口又变违法。左右指针各自最多走 l 的总位移上限锁死。
"恰好 K 个不同字符" 为什么不能直接滑窗? 怎么转化?
恰好 K 时,扩张可能跳到 K+1(违法),收缩可能跳到 K-1(也违法),两个方向都没单调性,while 循环没法维护。
转化: 恰好 K = 至多 K - 至多 (K-1)。”至多 K” 有单调性(窗口越大越可能超 K),可以标准滑窗。
模板 A 里,为什么 `ans` 必须在 while 外更新?
while 里窗口正在”从违法收回合法”的过程中,中间状态都是违法的。出 while 才保证 [left..right] 是当前合法窗口,而且是以 right 为右端能达到的最大合法窗口。在违法时更新 ans 会污染答案。
LeetCode 练习
热身:
- 3. 无重复字符的最长子串 — 模板 A 裸题
- 209. 长度最小的子数组 — 模板 B 裸题
- 438. 找到字符串中所有字母异位词 — 定长窗口变体
- 1004. 最大连续 1 的个数 III — 模板 A,”违法 = 0 的个数 > K”
进阶:
- 76. 最小覆盖子串 — 模板 B +
have计数优化 - 992. K 个不同整数的子数组 — 恰好 K = 至多 K - 至多 K-1
- 239. 滑动窗口最大值 — 定长 + 单调队列,变招
- 395. 至少有 K 个重复字符的最长子串 — 没单调性,要分治或枚举不同字符数