
前言
滑动窗口本质上是双指针的一种特殊形式,两个指针left、right划定一个窗口区间,通过不断移动右指针扩大窗口,满足 / 不满足条件时移动左指针收缩窗口,避免暴力枚举所有子区间,把 O (n²) 复杂度优化到 O (n)。
滑动窗口分两大类:
- 固定窗口:窗口大小不变;
- 可变窗口(动态窗口):窗口大小随条件伸缩,本篇两道都是可变窗口,也是面试高频题型。
- 长度最小的子数组:满足条件找最小窗口
- 无重复字符的最长子串:满足条件找最大窗口
题目一:长度最小的子数组209. 长度最小的子数组 - 力扣(LeetCode)
题目描述

给定一个含有 n 个正整数的数组和一个正整数 target。 找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
cpp
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是满足条件的最小长度子数组。
思路分析
暴力思路:枚举所有起始位置,不断累加,找到满足和≥target 的子数组,时间复杂度 O (n²),数组长度大时会超时。
滑动窗口思路(可变窗口,求最小窗口):
- 定义左右指针
left = 0,窗口内总和sum = 0; right指针不断向右移动,把nums[right]加入窗口总和;- 当窗口总和 ≥ target ,说明当前窗口满足条件:
- 更新最小窗口长度;
- 尝试收缩左边界:
sum -= nums[left],left++,继续判断窗口是否依然满足条件,持续缩小窗口寻找更优解;
- 遍历结束,返回最小长度。
关键点:数组全部是正数,窗口右扩总和变大,左缩总和变小,单调性保证滑动窗口可行。
C++ 代码
cpp
#include <iostream>
#include <vector>
#include <climits>
using namespace std;
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0;
int sum = 0;
int res = INT_MAX;
for (int right = 0; right < nums.size(); right++) {
sum += nums[right];
// 满足条件,收缩左边界
while (sum >= target) {
res = min(res, right - left + 1);
sum -= nums[left];
left++;
}
}
return res == INT_MAX ? 0 : res;
}
int main() {
vector<int> nums = {2,3,1,2,4,3};
cout << minSubArrayLen(7, nums) << endl;
return 0;
}
复杂度分析
- 时间复杂度:O (n),
left和right最多各遍历数组一次。 - 空间复杂度:O (1),仅使用常数变量。
题目二:无重复字符的最长子串3. 无重复字符的最长子串 - 力扣(LeetCode)
题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
示例:
cpp
输入: s = "abcabcbb"
输出: 3
解释: 无重复字符的最长子串是 "abc",其长度为 3。
思路分析
暴力思路:枚举所有子串,用集合判断是否有重复字符,O (n²),效率低。
滑动窗口思路(可变窗口,求最大窗口):
left = 0,右指针right不断向右扩大窗口;- 使用哈希集合(或数组)保存当前窗口内的字符;
- 如果
s[right]已经在窗口中:- 不断移除窗口最左侧字符
s[left],left++,直到窗口内不存在重复字符;
- 不断移除窗口最左侧字符
- 窗口合法后,记录窗口长度,更新最大值;
对比上一题: 长度最小子数组:满足条件就收缩左边界,尽可能缩小窗口 无重复最长子串:出现重复才收缩左边界,尽可能扩大窗口
C++ 代码
cpp
#include <iostream>
#include <string>
#include <unordered_set>
using namespace std;
int lengthOfLongestSubstring(string s) {
unordered_set<char> win;
int left = 0;
int maxLen = 0;
for (int right = 0; right < s.size(); right++) {
// 存在重复字符,收缩左边界
while (win.count(s[right])) {
win.erase(s[left]);
left++;
}
win.insert(s[right]);
maxLen = max(maxLen, right - left + 1);
}
return maxLen;
}
int main() {
string s = "abcabcbb";
cout << lengthOfLongestSubstring(s) << endl;
return 0;
}
复杂度分析
- 时间复杂度:O (n),左右指针最多遍历字符串一次;字符的插入删除是 O (1)。
- 空间复杂度:O (Σ),Σ 是字符集大小,最多存放所有不重复字符。
优化小技巧:如果限定是 ASCII 字符,可以用大小 128 的数组代替 unordered_set,速度更快。
cpp
// 数组版本
int lengthOfLongestSubstring(string s) {
int hash[128] = {0};
int left = 0, maxLen = 0;
for(int right = 0; right < s.size(); right++){
while(hash[s[right]] != 0){
hash[s[left]]--;
left++;
}
hash[s[right]]++;
maxLen = max(maxLen, right-left+1);
}
return maxLen;
}
两道题核心对比总结
表格
| 题目 | 窗口目标 | 收缩时机 | 特点 |
|---|---|---|---|
| 长度最小的子数组 | 求满足条件的最小窗口 | 窗口满足条件时,持续收缩左边界 | 数组全正数,和具备单调性 |
| 无重复字符的最长子串 | 求满足条件的最大窗口 | 窗口出现非法元素(重复字符)才收缩 | 维护窗口内元素唯一性 |
滑动窗口通用模板记忆:
cpp
left = 0
for right 遍历数组/字符串:
将right元素加入窗口
while 窗口不合法(或窗口满足条件):
更新答案
移除left元素,left++
更新答案
核心难点:判断 while 循环条件,以及什么时候更新结果。 ✅ 求最小窗口:while 满足条件,收缩,更新结果 ✅ 求最大窗口:while 不满足条件,收缩,更新结果
结尾
滑动窗口是数组、字符串面试的高频考点。两道题一个求最小窗口、一个求最大窗口,刚好覆盖可变滑动窗口两大经典场景。掌握两者的区别,遇到子数组、子串问题,可以优先思考是否能用滑动窗口优化暴力解法。
