滑动窗口算法详解
**滑动窗口(Sliding Window)**是解决数组或字符串子区间问题的利器。无论是'最小覆盖子串',还是'长度最小的子数组',滑动窗口都能以 O(n) 的时间复杂度优雅解决。
1. 为什么要学滑动窗口?
LeetCode 209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target。找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。
滑动窗口是一种解决数组或字符串子区间问题的高效算法,能将时间复杂度从 O(n²) 优化至 O(n)。通过 LeetCode 长度最小的子数组和最长无重复子串两个经典案例,对比暴力解法与滑动窗口策略,阐述连续性、单调移动、局部更新及最优性保证四大核心理念。提供了完整的 C++ 代码实现与执行逻辑分析,帮助读者掌握双指针技巧,提升算法面试解题能力。
**滑动窗口(Sliding Window)**是解决数组或字符串子区间问题的利器。无论是'最小覆盖子串',还是'长度最小的子数组',滑动窗口都能以 O(n) 的时间复杂度优雅解决。
给定一个含有 n 个正整数的数组和一个正整数 target。找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。
输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。
// 暴力解法 O(n²)
int minSubArrayLen_brute(int target, vector<int>& nums) {
int n = nums.size();
int minLen = INT_MAX;
// 枚举所有起始位置
for (int i = 0; i < n; i++) {
int sum = 0;
// 枚举所有结束位置
for (int j = i; j < n; j++) {
sum += nums[j];
if (sum >= target) {
minLen = min(minLen, j - i + 1);
break; // 找到了就 break,因为后面的更长
}
}
}
return minLen == INT_MAX ? 0 : minLen;
}
当 n=10000 时,需要约 1 亿次操作,严重超时。
假设我们有一个窗口 [2,3,1] 和为 6 < 7。
当前窗口:[2,3,1] 和=6 < 7 扔掉左边:移除 2 → [3,1] 和=4 加入右边:加入 2 → [3,1,2] 和=6 加入右边:加入 4 → [3,1,2,4] 和=10 ≥ 7 这样避免了重复计算 [3,1] 的和!
就像相机取景框,只移动边界,不重新拍摄整个画面。
int minSubArrayLen_sliding(int target, vector<int>& nums) {
int left = 0; // 窗口左边界
int sum = 0; // 当前窗口的和
int minLen = INT_MAX; // 最小长度
int n = nums.size();
for (int right = 0; right < n; right++) {
// 1. 加入右边元素(扩大窗口)
sum += nums[right];
// 2. 当窗口满足条件时,尝试收缩窗口
while (sum >= target) {
// 更新最小长度
minLen = min(minLen, right - left + 1);
// 收缩窗口:移除左边元素
sum -= nums[left];
left++;
}
// 3. 如果窗口不满足条件,继续扩大窗口(下一轮循环)
}
return minLen == INT_MAX ? 0 : minLen;
}
先向右扩大,当 sum 足够时,再从左缩小。
只处理连续的子序列,不是任意的子集。
左右指针只能单向移动(通常向右)。
int left = 0, right = 0;
while (right < n) {
// right 只会增加
// left 只会增加或不变
// 永远不会回头!
}
窗口从 [2,3,1] 滑动到 [3,1,2]。
通过合理的收缩规则,确保不会错过最优解。
while (窗口不满足条件) {
收缩窗口; // 剔除"坏"元素
}
// 此时窗口是当前右边界下的"局部最优"
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> charSet;
int left = 0, maxLen = 0;
for (int right = 0; right < s.size(); right++) {
while (charSet.find(s[right]) != charSet.end()) {
charSet.erase(s[left]);
left++;
}
charSet.insert(s[right]);
maxLen = max(maxLen, right - left + 1);
}
return maxLen;
}
};
set 是 C++ STL 中的一种关联容器,它存储唯一的元素(不允许重复),并且元素会自动排序。
#include <set>:有序集合(基于红黑树实现)#include <unordered_set>:无序集合(基于哈希表实现)unordered_set 来记录当前窗口里的字符,保证里面没有重复。left、right 表示窗口的左右边界,left 初始为 0。maxLen 记录遍历过程中最长的无重复子串长度。right 从左到右遍历字符串,每次尝试把 s[right] 加入窗口。s[right] 已经在 set 里(重复了),就不断从左边 erase 字符、left++,直到把重复的那个字符彻底移出窗口。s[right] 加入 set,更新窗口长度,维护 maxLen。maxLen 就是答案。它的作用是:当遇到重复字符时,持续移动左指针,直到移除那个重复字符。
我们用滑动窗口 [left, right] 来跟踪当前无重复字符的子串。
步骤分析:
left = 0, charSet = {}'a' 不在集合中,直接加入 → charSet = {'a'}, 长度=1'b' 不在集合中,直接加入 → charSet = {'a', 'b'}, 长度=2'c' 不在集合中,直接加入 → charSet = {'a', 'b', 'c'}, 长度=3'b' 已经在集合中!
while 循环s[left](即 s[0] = 'a'),left = 1'b' 还在集合中吗?是的,因为集合现在是 {'b', 'c'}s[left](即 s[1] = 'b'),left = 2{'c'},'b' 不在集合中了while 循环'b',集合变为 {'c', 'b'},长度 = right - left + 1 = 3 - 2 + 1 = 2'd' 不在集合中,直接加入 → charSet = {'c', 'b', 'd'}, 长度=3后续步骤依此类推,最终返回最大长度。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online