滑动窗口(同向区间指针)
滑动窗口是数组 / 字符串类题目里出镜率极高的套路。掌握它,能让一大批看似 O(n²) 的暴力解法瞬间降到 O(n)。本文从"定长"和"变长"两个视角,配合可直接套用的模板代码,帮你一次性理清思路。
一、什么是滑动窗口?
滑动窗口(Sliding Window)属于双指针 中的一种------同向指针 。两个指针 left 和 right 同时从数组左端出发,向右移动,圈出一个不断变化的"窗口"。
它的核心思想是:在窗口滑动的过程中,利用已经计算好的信息,避免重复计算。
根据窗口大小是否固定,可以分为两类:
| 类型 | 窗口大小 | 典型问法 |
|---|---|---|
| 定长滑动窗口 | 固定为 k |
"长度为 k 的子数组/子串中......的最大/最小值" |
| 变长滑动窗口 | 动态变化 | "满足 XX 条件的最长/最短子数组" |
二、定长滑动窗口(固定相框)📷
想象你拿着一个尺寸固定的相框 ,在数组上从左往右一格一格地平移。每移动一次,画面里就进来一个新元素,挤出去一个老元素。
套路三步走
- 初始化 :先计算前
k个元素构成的第一个窗口的状态(和、计数、最大值等)。 - 滑动 :窗口整体向右平移,每次 加一个新进来的
nums[i],减一个被踢出去的nums[i-k]。 - 更新答案:在每次滑动后记录当前窗口的结果。
全程只需一层循环,时间复杂度 O(n)。
模板代码
jsx
// 1. 初始化第一个长度为 k 的窗口
let sum = 0;
for (let i = 0; i < k; i++) {
sum += nums[i]; // 累加状态
}
let res = sum;
// 2. 开始滑动
for (let i = k; i < nums.length; i++) {
// 进一个 nums[i],出一个 nums[i-k]
sum += nums[i] - nums[i - k];
// 更新最大/最小值
res = Math.max(res, sum);
}
return res;
典型例题
- 🔹 LeetCode 643:子数组最大平均数 I
- 🔹 LeetCode 1456:定长子串中元音的最大数目
- 🔹 LeetCode 2090:半径为 k 的子数组平均值
三、变长滑动窗口(毛毛虫)🐛
变长窗口更像一只毛毛虫 :头(right)先往前拱一拱,拱到不舒服了,尾巴(left)再跟上来缩一缩,如此反复向前爬行。
套路要点
- 外层循环 :右指针
right不断向右拉伸,把新元素纳入窗口。 - 内层循环 :一旦窗口满足/违反 某种条件,左指针
left立即向右收缩,直到条件恢复。 - 关键 :根据题目要求的是"最长 "还是"最短 ",决定在收缩之前 还是之后更新答案。
💡 小技巧:
找最短 → 在 while 内部(窗口刚满足条件时)更新答案,然后继续缩;
找最长 → 在 while 外部(窗口刚恢复合法时)更新答案。
模板代码
JavaScript
let left = 0, right = 0;
let res = 0; // 或 Infinity,视题意而定
while (right < nums.length) {
// 【1】入窗:扩展右边界
// 把 nums[right] 加入窗口,更新状态
while (/* 窗口需要收缩 */) {
// 【2】记录答案:找「最短」时在这里更新 res
// 【3】出窗:收缩左边界
// 把 nums[left] 移出窗口,更新状态
left++;
}
// 【4】记录答案:找「最长」时在这里更新 res
right++;
}
return res;
典型例题
- 🔹 LeetCode 209:长度最小的子数组(找最短)
- 🔹 LeetCode 3:无重复字符的最长子串(找最长)
- 🔹 LeetCode 76:最小覆盖子串(找最短 + 哈希计数)
- 🔹 LeetCode 438:找到字符串中所有字母异位词
四、使用滑动窗口的信号 🚦
当题目同时满足以下特征时,优先考虑滑动窗口:
- 处理连续的子数组 / 子串 / 子序列。
- 求满足某条件的最长 / 最短 / 定长区间。
- 数据具有单调性:窗口扩大时某个指标单调变化(如和变大、字符种类变多)。
⚠️ 注意 :如果数组中包含负数,窗口和就不再单调,此时滑动窗口可能失效,需要考虑前缀和 + 哈希表等其它思路。