滑动窗口_长度最小的子数组_C++
1.题目解析
leedcode题目链接:https://leetcode.cn/problems/2VG8Kg/
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ targe
的长度最小的 连续子数组 [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)什么是滑动窗口?
2)什么时候用滑动窗口?
3)怎么用滑动窗口?
4)滑动窗口的正确性(滑动窗口为什么是对的?)
5)滑动窗口的时间复杂度。
2.算法原理
2.1暴力解法:暴力枚举出所有的子数组的和
1.纯暴力枚举
暴力枚举出所有子数组,并求它们的和,找出符合要求的子数组,然后再找出最优的子数组。
以[2, 3, 1, 2, 4, 3]
为例,我们先定义一个枚举的区间,比如是下标0到3之间的数[2, 3, 1, 2]
,对这个区间求和,需要遍历一遍数组,是O(n)
。而遍历枚举区间,又需要用到两层for循环,是一个O(n^2)
的时间复杂度。综上,纯暴力枚举的时间复杂度是O(n^3)
。
2.优化暴力解法
还是以[2, 3, 1, 2, 4, 3]
为例,target
为7:
我们先定义两个指针left
和right
,它们开始时都指向0这个位置。再定义一个变量sum
,用来记录子数组中所有元素的和。
1)最开始,子数组中只有元素2,我们让
sum = 2
,然后让right++
,right
此时指向的位置值是3。
2)此时子数组为[2, 3]
,sum
更新为2+3=5,然后再让right++
,依次类推。
3)当right
已经指向最后一个位置时,再更新两个指针的位置,让它们再从1的位置重复上述操作。
之前,纯暴力解法时,为了计算子数组的和,需要再遍历一遍子数组,时间复杂度是一个O(n)
,加上两层for
循环的复杂度,时间复杂度为O(n^3)
。而这种双指针的优化写法,省去了遍历子数组求和的操作,使得时间复杂度直接降低了一维,只有两个指针不断遍历的时间复杂度,为O(n^2)
。
2.2利用规律,使用“同向双指针”来解题
注意题中的一个条件:数组中所有的数,都是大于等于0的。所以如果选出一个子数组,它已经满足条件了,那么再向其中增加元素,数组的总和一定是增大或者不变的,那就没必要再向子数组中增加元素了。我们能否根据这个性质,来做一些优化?
1.以2.1中的例子为例:
如图,我们按照优化的暴力算法,已经让right
指向了下标为3的位置,然后更新sum
,发现这是一个满足条件的子数组。那么根据最开始所说的规律,如果此时我们再让right++
,再向子数组中增加新的元素,它的结果一定是满足条件的,但是长度会变长,最后也一定不是我们想要的长度最小的子数组。
所以我们可以把right
向后移动所产生的一串数组大胆舍弃。为了达到这个效果,我们可以固定right
不动,然后移动left
,让left++
,得到新的子数组,更新sum
,再判断这个新的子数组sum
是否大于等于target
。当满足条件时,我们更新长度len
,再让left++
,进行下一轮判断;当不满足时,我们让right++
,再进行下一轮判断。以此类推。
我们不难发现,指针left
和right
,是同向移动的,我们称之为“同向双指针”。而且这样的一对同向双指针,共同维护了一小块区间内的数据,且这一小块区间不断地向后滑动,我们形象的称其为:“滑动窗口”。
2.滑动窗口如何用?
1)先定义两个指针left
和right
,让它们都初始化为0。
2)构建窗口,进窗口。
3)根据判断条件,判断是否要出窗口。
其中2和3步一直循环。
什么时候更新结果呢?这个要就提论题。有的题是在进窗口的时候更新结果,有的题是在判断的时候更新结果。就本题而言,我们要先判断,然后要更新结果,再出窗口。
3.滑动窗口的正确性
虽然,我们没有将所有的子数组都枚举出来,进行判断。但是,我们考虑了所有的情况,只是把很多不合适的数组提前舍弃了,规避了很多没有必要的枚举行为,所以是正确的。
4.时间复杂度分析
在整个过程中,只有left
和right
在移动。最坏的情况下,right
移动到最后一个数,left
也移动到最后一个数,时间复杂度是n+n,一共才O(n)
。
3.代码编写
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int left = 0, right = 0;
int len = INT_MAX; // 长度,初始化为int的最大值
int sum = nums[0];
while(left <= right)
{
if(sum >= target)
{
len = min(len, right - left + 1); // 更新结果
sum -= nums[left];
left++;
}
else
{
right++;
if(right >= nums.size()) break; // 防止越界
sum += nums[right];
}
}
if(left == 0 && right >= nums.size()) return 0; // 如果if中的条件满足,说明没有合适的子数组满足条件,返回长度0
return len;
}
};