滑动窗口 · Sliding Window
很多「连续子数组 / 子串」问题,暴力写法都要枚举所有区间再逐个求值,嵌套两层循环 O(n·k) 甚至 O(n²)。但这些区间是连续滑过的 —— 相邻两个窗口大部分元素重叠,只差一进一出。抓住这点,用一个随下标单向移动的窗口、只做增量维护,就能把整个过程压成一遍 O(n) 扫描。滑动窗口不是单个算法,而是一类方法,按窗口宽度是否固定分为两大类:
其一,定长窗口:宽度固定为 k,从左滑到右。窗口右移一格只换掉一进一出两个数 —— 能增量维护的聚合 (和 / 计数) 一次加减就更新完;但最值这种不能简单增量的,需要单调队列。其二,变长窗口(毛毛虫 caterpillar):宽度由条件驱动伸缩 —— 右指针 R 扩张,左指针 L 在条件被破坏 / 已满足时收缩,L、R 都只增不减,各扫一遍。
前置知识:本系列建立在双指针 Two Pointer 的「同向双指针」流派之上 —— 变长窗口的 L / R 正是一对同向移动的指针,建议先了解该系列的总览。每页都能改输入、单步推进,观察窗口在数组 / 字符串上滑动(青色 = 当前窗口 / 左指针 / 紫色 = 右指针 / 黄色 = 至今最优窗口),旁边代码逐行点亮。
定长窗口:宽度恒为 k,出一进一
窗口和:出一个、进一个,不必重算
最基础的入门题:宽 k 的窗口求窗内和 / 均值(LeetCode 643)。关键观察——窗口右移一格只滑走最左、滑进最右,中间 k-2 个数没变,所以新和 = 旧和 -出去的+进来的,一次加减就更新。这个「增量更新」是滑动窗口的核心,把朴素 O(n·k) 压到 O(n)。单步观察窗口滑动、窗内和如何一减一加地更新。
窗内取最值:为什么不能简单「出一进一」
把聚合从「和」换成「最大值」(LeetCode 239),增量更新就失效了:滑走的若恰是当前最大,新最大值无法靠一次减法算出来,得在剩下 k-1 个里重找。朴素法只能每个窗口把 k 个数从头比一遍,共 O(n·k)——这一页单步展示「重复比较」浪费在哪,引出后面两种 O(n) 解法。
双端单调队列:及时舍弃「不可能成为最大值」的数
关键性质:扫到一个数,它前面那些比它小、又比它早的数,不可能再成为后续任何窗口的最大值。于是用一个双端队列存下标、维持值单调递减:新数从队尾把所有 ≤ 它的弹出再入队,队首永远是当前窗口最大值;队首滑出窗口就从前端弹出。每个下标只进出各一次 → 整体 O(n)。单步观察队尾淘汰、队首输出最大值、数组与队列同步高亮。
分块法:按 k 切块,预处理块内前缀 / 后缀最大值
另一条路(sparse table 的近亲):把数组每 k 个切成一块。任意长 k 的窗口,要么是一整块、要么横跨相邻两块。预处理出每个位置到块开头的最大值 lmax 与到块结尾的最大值 rmax,窗口 [L,R] 的最大值就是 max(rmax[L], lmax[R])——左半截 + 右半截拼起来。预处理 O(n)、查询 O(1)。单步观察窗口如何被切成两截。
变长窗口:宽度由条件驱动伸缩
无重复字符的最长子串:遇到重复就把左指针跳过去
LeetCode 3。窗口宽度不再固定:右指针 R 不断纳入新字符把窗口撑长;一旦窗内出现重复字符,就把左指针 L 跳到那个重复字符的右边,缩回「无重复」。每个 R 处的合法窗口长 R-L+1 取最大即答案。L 一步到位、绝不回退,所以是 O(n) 而非 O(n²)。单步观察毛毛虫式的「扩-缩」与左指针的跳跃。
长度最小的子数组:右扩到「达标」,再尽力缩左
LeetCode 209,目标反过来求最短:给正整数数组与 target,找和 ≥ target 的最短连续子数组。右指针纳入新数把窗口和撑到达标,就反过来缩左——在「仍达标」的前提下尽量移除左边的数,每缩一步记一次长度。和「无重复最长子串」正好是求最长 / 求最短的镜像:一个「被迫缩时记录」、一个「还能缩时记录」。单步观察窗口和的涨落与左右边界的伸缩。
落地:生产代码中的滑动窗口
应用实例:生产代码中的滑动窗口与单调队列
一个可动手体验的真实场景:用两个单调队列实时维护传感器数据滑动窗口的最大 / 最小值(峰谷监控、最近 k 秒极差告警),把朴素的「每步重扫 k 个」压到均摊 O(1)。再梳理滑动窗口的其他应用场景:单调队列优化 DP、流式窗口聚合(Flink / 时序数据库)、限流的滑动窗口计数器,以及一组同源 LeetCode 题 (3 / 76 / 209 / 239 / 862 / 1438)。
O(n)、实现简单、可扩展为流式)。变长则看求最长还是最短、收缩条件是什么。三者都建立在「窗口单向滑动 + 增量维护」这同一内核上,按问题形态采取了不同形式。🔗 相关链接
- 3. 无重复字符的最长子串 · LeetCode 变长窗口求最长的入门题,本系列「无重复字符的最长子串」页取自这里。
- 209. 长度最小的子数组 · LeetCode 变长窗口求最短的代表题,本系列「长度最小的子数组」页取自这里。
-
239. 滑动窗口最大值
· LeetCode
定长窗口取最值的经典题,本系列单调队列 / 分块两页的示例数组与
k取自这里。 - 单调队列 · OI Wiki 单调队列的定义、滑动窗口模板,以及它在单调队列优化 DP 里的标准用法。
- Sparse Table · cp-algorithms.com 分块 / 倍增预处理静态区间最值 (RMQ) 的权威讲解,与本系列「分块法」同源。