题目
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
提示:
1 <= target <= 10^9
1 <= nums.length <= 10^5
1 <= nums[i] <= 10^5
解法一 暴力解法
思路:
枚举所有的情况 确定起始位置 依次确定每一个终点位置 算出每一个长度的总和 然后取一个长度最小的
出现的错误思路:
双层for循环中 第二个循环是从起始位置的后一个位置开始移动 这就使得我得现在第一个循环中进行累加 第二个循环中提前判断是否sum>target
这样的设置会有很多bug需要提前规避 比如:
1.起始位置的数不满足要求 但是count比较小 让程序直接以为这一个数字是满足要求的
2.退出循环的那个数字也是满足的 但是因为提前一个判断总和 所以会漏掉包括最后一个数字的子数组的情况
这些小点很容易在写代码忘记 所以得拿用例去模拟 才能发现bug
int minSubArrayLen(int target, int* nums, int numsSize){
int result=numsSize+1;
for(int i=0;i<numsSize;i++){
int flag=0;
int sum=nums[i];
int count=1;
for(int j=i+1;j<numsSize;j++){
if(sum>=target){
flag=1;
break;
}else{
sum=sum+nums[j];
count++;
}
}
if(sum>=target){
flag=1;
}
if(count<result&&flag==1)
result=count;
}
if(result==numsSize+1)
return 0;
return result;
}
改进
就是第二层循环从起始位置开始遍历 这样就可以把起始位置的元素放到第二层循环中累加
在判断是否大于sum的时候就进行子段长度的保存
int minSubArrayLen(int target, int* nums, int numsSize){
int result=numsSize+1;
for(int i=0;i<numsSize;i++){
int sum=0,count;
for(int j=i;j<numsSize;j++){
sum=sum+nums[j];
if(sum>=target){
count=j-i+1;
if(count<result){
result=count;
}
break;
}
}
}
if(result==numsSize+1)
return 0;
else
return result;
}
解法二 滑动窗口
思路
根据暴力解法可以得出该问题主要是要找到长度最小的数组的起始位置和结束位置,那么为了避免冗余的枚举过程,我们希望可以动态的去枚举这些情况。窗口则是起始位置到结束位置。
窗口如何滑动?
网上有些人是说确定终点枚举起始位置
我的理解是 窗口是从最一开始的一个元素(起始位置) 然后不断扩大 也就是在终点位置 当第一次找到符合条件的窗口时 以该起点为起点的所有情况中的最优已经找到了 所以窗口缩小也就是开始变换起点位置(此时必然川口总和小于target)那么在开始选择终点位置。
那么for循环的循环变量 为终点位置 是因为终点位置是每一个都遍历到的 而起始位置会根据窗口的大小动态选择的
当达到窗口内数字总和大于或等于target时 停止枚举 则找到一个窗口符合要求 记录该窗口的长度 可以开始枚举下一个终点位置
时间复杂度
虽然是两个循环 但是时间复杂度是O(n) 原因是每个元素都最多只会被遍历两遍 先进窗口再出窗口 那么时间复杂度是O(2n)=O(n)
int minSubArrayLen(int target, int* nums, int numsSize){
int result=numsSize+1;
int i=0;
int sum=0;
for(int j=0;j<numsSize;j++){
sum=sum+nums[j];
while(sum>=target){
int count=j-i+1;
if(count<result){
result=count;
}
sum=sum-nums[i];
i++;
}
}
if(result==(numsSize+1))
return 0;
return result;
}