文章目录
一、题目解析
题目中提到了正整数的数组,这个需要注意一下,和我们接下来提到的单调性有关。我将以示例1来解析题目
这里我枚举了三个连续子数组,根据题目描述,我们需要返回sum>=target且长度最小的连续子数组的长度,即[4,3],len=2,而这题的答案也为2。
二、算法原理
解法一:暴力枚举
先用一个循环固定left,然后让right从left开始枚举所有连续子数组,接着循环遍历[left,right],求出sum,并且判断是否符合sum>=target,符合则用min更新,不符合则让right继续往右走。(这是最搓的方式,不用想肯定超时,时间复杂度为O(n^3)。
我们可以将其优化一下,在right边走的时候边用sum记录连续子数组的和,就可以少一层循环,然后在找到第一个连续子数组的时候更新记录,并且跳出循环,因为数组中都是正整数,继续加上下一个nums[right]只会>=target,且len会增加,我们求的是最小的len,所有无需继续枚举,直接跳到下一个left。(这就是单调性,时间复杂度为O(n^2)。
解法二:利用单调性+“同向双指针”(滑动窗口)优化
我们注意到本题中,left和right都往同一个方向移动,并且我们在暴力枚举的过程中right每次都要回退到left的位置,这边我们可以优化一下,在示例1中,[2,3,1,2]刚好符合第一个连续子数组且sum>=target,接下来我们只需让sum-=nums[left],left++即可
a.什么是滑动窗口?
这边就可以判断一次是否sum>=target,是则更新记录,否则right++,进入下一个判断,我们可以发现left和right现在都是同向的且不会回退的,这就是我们要讲滑动窗口的含义。
b.什么时候用滑动窗口?
答案就是我们一直再强调的单调性了。
c.怎么用滑动窗口?
这里要注意的是更新结果的位置每题都不完全一样,它有可能是在进窗口的时候就可以更新结果,而有的题是在判断结束之后,出窗口之前就更新结果,需要就题论题,而本题就是判断之后需要更新结果再出窗口。
d. 正确性
正如我在解法一中所说,利用了单调性,规避了很多没有必要的行为
e.时间复杂度
虽然我们的代码中使用了两个循环,但是我们看算法思想,left和right各遍历了一遍数组,也就是O(2n),即时间复杂度为O(n)。
三、代码实现
解法一:暴力枚举——O(n^2)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = INT_MAX,n = nums.size();//用len记录结果
for(int left = 0;left < n;left++)//开始位置
{
int sum = 0;//记录连续子数组的和
for(int right = left;right < n;right++)//寻找结束位置
{
sum += nums[right];
if(sum >= target)
{
len = min(len,right - left + 1);//不断更新至最小值
break;
}
}
}
return len == INT_MAX ? 0 : len;
}
};
解法二:滑动窗口——O(n)
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int sum = 0,len = INT_MAX,n = nums.size();//用len记录结果
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;
}
};