欢迎拜访 :雾里看山-CSDN博客
本篇主题 :算法思想之滑动窗口(一)
发布时间 :2025.4.6
隶属专栏 :算法

目录
滑动窗口算法介绍
滑动窗口是一种用于处理数组/字符串子区间问题的高效算法,通过维护动态窗口减少重复计算,将时间复杂度从暴力法的 O(n²) 优化至 O(n)。
核心思想
- 窗口定义 :用两个指针(
left
和right
)表示当前处理的区间[left, right]
。 - 窗口移动:根据问题规则,右指针扩展窗口,左指针收缩窗口,动态调整窗口大小。
- 核心优势:避免重复遍历元素,每个元素最多被访问两次。
时间复杂度
O(n),左右指针各遍历一次数组。
适用场景
- 子数组/子串的极值问题(最大、最小)。
- 需要统计满足条件的连续元素序列。
- 窗口内元素满足单调性或有明确的收缩规则。
注意事项
- 边界处理:空输入、窗口大于数组长度等情况。
- 数据结构选择:根据问题使用哈希表、数组等记录窗口状态。
- 负数处理:如求最大子数组和时,可能需要动态调整窗口起始位置(此时需结合前缀和或 Kadane 算法)。
例题
长度最小的子数组
题目链接
题目描述
给定一个含有 n 个正整数的数组和一个正整数
target
。找出该数组中满足其总和大于等于 target 的长度最小的 子数组
[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度。如果不存在符合条件的子数组,返回0
。示例 1:
输入 :
target = 7, nums = [2,3,1,2,4,3]
输出 :2
解释 :子数组[4,3]
是该条件下的长度最小的子数组。示例 2:
输入 :
target = 4, nums = [1,4,4]
输出 :1
示例 3:
输入 :
target = 11, nums = [1,1,1,1,1,1,1,1]
输出 :0
提示:
1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 104
算法思路
由于此问题分析的对象是一段连续的区间 ,因此可以考虑滑动窗口 的思想来解决这道题。
让滑动窗口满足:从 i
位置开始,窗口内所有元素的和小于 target
(那么当窗口内元素之和第一次大于等于目标值的时候,就是i
位置开始,满足条件的最小长度)。
做法:将右端元素划入窗口中,统计出此时窗口内元素的和:
- 如果窗口内元素之和大于等于
target
:更新结果,并且将左端元素划出去的同时继续判断是否满足条件并更新结果(因为左端元素可能很小,划出去之后依旧满足条件) - 如果窗口内元素之和不满足条件:
right++
,另下⼀个元素进⼊窗口。
代码实现
cpp
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int n = nums.size(), sum = 0, len = INT_MAX;
for(int left = 0, right = 0; right < n; right++)
{
sum += nums[right];// 进窗口
while(sum >= target)// 判断
{
len = min(len, right - left + 1);// 更新结果
sum -= nums[left++];// 出窗口
}
}
return len == INT_MAX ? 0 : len;
}
};

无重复字符的最长子串
题目链接
题目描述
给定一个字符串
s
,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:输入 :
s = "abcabcbb"
输出 :3
解释 : 因为无重复字符的最长子串是"abc"
,所以其长度为3
。示例 2:
输入 :
s = "bbbbb"
输出 :1
解释 : 因为无重复字符的最长子串是"b"
,所以其长度为1
。示例 3:
输入 :
s = "pwwkew"
输出 :3
解释 : 因为无重复字符的最长子串是"wke"
,所以其长度为3
。请注意,你的答案必须是 子串 的长度,
"pwke"
是一个子序列,不是子串。提示:
0 <= s.length <= 5 * 104
s
由英文字母、数字、符号和空格组成
算法思路
在往后寻找无重复子串能到达的位置时,可以利用哈希表统计出字符出现的频次,来判断什么时候子串出现了重复元素。
研究的对象依旧是一段连续的区间,因此继续使用滑动窗口思想来优化。
让滑动窗口满足:窗口内所有元素都是不重复的。
做法:右端元素 ch
进入窗口的时候,哈希表统计这个字符的频次:
- 如果这个字符出现的频次超过
1
,说明窗口内有重复元素,那么就从左侧开始划出窗口,直到ch
这个元素的频次变为1
,然后再更新结果。 - 如果没有超过
1
,说明当前窗口没有重复元素,可以直接更新结果。
代码实现
cpp
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int hash[128] = {0};
int n = s.size(),len = 0;
for(int left = 0, right = 0; right < n; right++)
{
hash[s[right]]++;// 进窗口
while(hash[s[right]] > 1)// 判断
{
hash[s[left++]]--;// 出窗口
}
len = max(right - left + 1, len);// 更新结果
}
return len;
}
};

三数之和最大连续1的个数 III
题目链接
题目描述
给定一个二进制数组
nums
和一个整数k
,假设最多可以翻转k
个0
,则返回执行操作后 数组中连续1
的最大个数 。示例 1:
输入 :
nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出 :6
解释 :[1,1,1,0,0,1 ,1,1,1,1,1 ]粗体数字从
0
翻转到1
,最长的子数组长度为6
。示例 2:
输入 :
nums = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出 :10
解释 :[0,0,1,1,1 ,1 ,1,1,1,1 ,1,1,0,0,0,1,1,1,1]粗体数字从
0
翻转到1
,最长的子数组长度为10
。提示:
1 <= nums.length <= 105
nums[i]
不是0
就是1
0 <= k <= nums.length
算法思路
不要去想怎么翻转,不要把问题想的很复杂,这道题的结果无非就是一段连续的 1
中间塞了 k
个 0
嘛。
因此,我们可以把问题转化成:求数组中一段最长的连续区间,要求这段区间内 0
的个数不超过 k
个。
既然是连续区间,可以考虑使用滑动窗口来解决问题。
代码实现
cpp
class Solution {
public:
int longestOnes(vector<int>& nums, int k) {
int n = nums.size(), sum = 0, len = 0;
for(int left = 0, right = 0; right < n; right++)
{
if(nums[right] == 0)//进窗口
sum++;
while(sum > k)//判断
{
if(nums[left++] == 0)// 出窗口
sum--;
}
len = max(len, right - left + 1);// 更新结果
}
return len;
}
};

将 x 减到 0 的最小操作数
题目链接
题目描述
给你一个整数数组
nums
和一个整数x
。每一次操作时,你应当移除数组nums
最左边或最右边的元素,然后从x
中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。如果可以将
x
恰好 减到0
,返回 最小操作数 ;否则,返回-1
。示例 1:
输入 :
nums = [1,1,4,2,3], x = 5
输出 :2
解释 :最佳解决方案是移除后两个元素,将x
减到0
。示例 2:
输入 :
nums = [5,6,7,8,9], x = 4
输出 :-1
示例 3:
输入 :
nums = [3,2,20,1,1,3], x = 10
输出 :5
解释 :最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将x
减到0
。提示:
1 <= nums.length <= 105
1 <= nums[i] <= 104
1 <= x <= 109
算法思路
题目要求的是数组左端+右端 两段连续的、和为x
的最短数组,信息量稍微多一些,不易理清思路;我们可以转化成求数组内一段连续的、和为sum(nums) - x
的最长数组。此时,就是熟悉的滑动窗口问题了。
代码实现
cpp
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int sum = 0;
for(auto &num : nums)
{
sum+=num;
}
int target = sum - x;
if(target < 0)
return -1;
int n = nums.size(), len = -1,temp = 0;
for(int left = 0, right = 0; right < n; right++)
{
temp += nums[right];// 进窗口
while(temp > target)// 判断
{
temp -= nums[left++];// 出窗口
}
if(temp == target)// 更新结果
len = max(len, right - left + 1);
}
return len == -1 ? -1 : n - len;
}
};

⚠️ 写在最后:以上内容是我在学习以后得一些总结和概括,如有错误或者需要补充的地方欢迎各位大佬评论或者私信我交流!!!