外观
滑动窗口技巧详解:双指针算法的5个高频场景与代码模板
「找出最长的无重复字符子串」「长度为 k 的子数组最大和」「两数之和 II」——这些高频算法题背后是同一个套路:暴力解法都是双重循环 O(n²),而窗口与指针的移动把它压到 O(n)。滑动窗口本质上是双指针的一个特例:两个指针同向移动,中间夹出一个动态区间,区间内的信息增量维护、不重复计算。本文按五类高频场景拆解这个套路的适用条件与代码模板。
先判断能不能用:窗口题的三个特征
不是所有区间问题都能滑窗,动手前先看三个特征:连续性——目标对象必须是连续的子数组/子串,跳着选元素的不行;单调性——窗口扩大时指标单调变化(和不减、计数不减),缩小时反向单调,这样才能用两个指针夹出答案;可增量维护——窗口内的统计量(和、计数、哈希表)能在指针移动时 O(1) 更新。三个条件都满足,就可以放心套模板。
场景一:定长窗口——固定 k 求极值
最基础的形态:窗口大小固定为 k,求窗口内的最大和或均值。套路是先建立第一个窗口,之后每次「进一个、出一个」:
javascript
// 长度为 k 的子数组最大和:O(n)
function maxSumFixed(nums, k) {
let sum = 0;
for (let i = 0; i < k; i++) sum += nums[i]; // 建立首个窗口
let max = sum;
for (let i = k; i < nums.length; i++) {
sum += nums[i] - nums[i - k]; // 进 i 出 i-k,增量更新
max = Math.max(max, sum);
}
return max;
}场景二:变长窗口求最长——「不重复字符子串」类
窗口长度不固定,目标是找到满足条件的最长窗口。模板是右指针一直扩张,条件被破坏时才收缩左指针:
javascript
// 无重复字符的最长子串:O(n)
function lengthOfLongestSubstring(s) {
const pos = new Map(); // 字符 → 最近出现的下标
let left = 0, best = 0;
for (let right = 0; right < s.length; right++) {
const ch = s[right];
if (pos.has(ch) && pos.get(ch) >= left) {
left = pos.get(ch) + 1; // 重复字符在窗口内:左指针直接跳过它
}
pos.set(ch, right);
best = Math.max(best, right - left + 1);
}
return best;
}场景三:变长窗口求最短——「最短覆盖子串」类
与场景二方向相反:先扩张到满足条件,再尽量收缩求最短。经典题是「长度最小的子数组」(和 ≥ target):
javascript
// 和 ≥ target 的最短子数组:O(n)
function minSubArrayLen(target, nums) {
let left = 0, sum = 0, best = Infinity;
for (let right = 0; right < nums.length; right++) {
sum += nums[right]; // 扩张
while (sum >= target) { // 满足条件时尽量收缩
best = Math.min(best, right - left + 1);
sum -= nums[left++];
}
}
return best === Infinity ? 0 : best;
}记忆口诀:求最长,破坏才缩;求最短,满足就缩。两类模板的骨架完全一致,区别只在收缩时机与答案更新位置。
场景四与场景五:快慢双指针与相向双指针
快慢双指针处理原地去重、移除元素这类原地修改:快指针探路,慢指针维护「已处理区」的尾部,如有序数组原地去重就是快指针遇新元素写入慢指针位置。相向双指针则一个从头一个从尾向中间收拢,利用序列的单调性跳过无效组合:「两数之和 II」中,和大了左移右指针、和小了右移左指针,每步都能确定性地淘汰一个候选,把 O(n²) 降为 O(n)。
复杂度与易错点
| 场景 | 指针移动 | 典型题目 | 复杂度 |
|---|---|---|---|
| 定长窗口 | 同向,同步 | 子数组最大和 I | O(n) |
| 变长求最长 | 同向,破坏才缩 | 无重复字符子串 | O(n) |
| 变长求最短 | 同向,满足就缩 | 长度最小子数组 | O(n) |
| 快慢指针 | 同向,异速 | 原地去重 | O(n) |
| 相向指针 | 从两端向中间 | 两数之和 II | O(n) |
两个高频易错点:其一,左指针收缩必须用 while 而不是 if——条件可能要连续收缩多次才能恢复;其二,求最短/求最长的初始值分别用 Infinity 与 0,别用 -1 混着判空。滑动窗口的每个模板都值得手写到肌肉记忆,因为面试现场留给你的思考时间,往往只够识别「这是哪一类窗口」。
实践清单
- 动手前先验证连续性、单调性、可增量维护三特征。
- 记牢口诀:求最长破坏才缩,求最短满足就缩。
- 收缩循环用 while,统计量更新与指针移动严格配对。
- 用 [1,1,1]、全相同数组、空数组三个用例检验边界。
滑动窗口是「用空间换时间的重计算」的典型反例——它证明的是:只要区间指标能增量维护,O(n²) 的枚举就永远有被压成 O(n) 的机会。识别这种机会的能力,正是算法训练真正的产出。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。