1.题目链接:209.长度最小的子数组
2.题目描述:
给定一个含有 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] <= 105
进阶:
如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
3.解法一(暴力求解)(会超时):
算法思路:
「从前往后」枚举数组中的任意一个元素,把它当成起始位置。然后从这个「起始位置」开始,然后寻找一段最短的区间,使得这段区间的和「大于等于」目标值。
将所有元素作为起始位置所得的结果中,找到「最小值即可」
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
//记录结果
int ret = INT_MAX;
int n = nums.size();
//枚举出所有满足和大于等于 target 的子数组[start,end]
//由于是取到最小,因此枚举的过程中要尽量让数组的长度最小
//枚举开始位置
for(int start = 0; start < n;start++){
int sum = 0;//记录从这个位置开始的连续数组的和
//寻找结束位置
for(int end = start ;end < n ; end++){
sum += nums[end];//将当前位置加上
if(sum >= target)//当这段区间内的和满足条件时
{
//更新结果,start 开头的最短区间已经找到
ret = min(ret , end - start +1);
break;
}
}
}
return ret == INT_MAX ? 0 : ret;
}
};
4.解法二(滑动窗口):
算法思路:
由于此问题分析的对象是「一段连续的区间」,因此可以考虑「滑动窗口」的思想来解决这道题。
让滑动窗口满足:从 i 位置开始,窗口内所有元素的和小于 target (那么当窗口内元素之和第一次大于等于目标值的时候,就是 i 位置开始,满足条件的最小值)
做法:将右端元素划入窗口中,统计出此时窗口内元素的和:
-
如果窗口内元素之和大于等于 target :更新结果,并且将左端元素划出去的同时继续判断是否满足条件并更新结果(因为左端元素可能很小,划出去之和依旧满足条件)
-
如果窗口内元素之和不满足条件:right++ ,另下一个元素进入窗口。
相信科学(这也是很多题解以及帖子没告诉你的事情:只给你说这么做。没给你解释为什么这么做):
为什么滑动窗口可以解决问题,并且时间复杂度更低?
-
这个窗口寻找的是:以当前窗口最左侧元素(记为 left1 )为基准,符合条件的状况,也就是在这道题中,从 left1 开始,满足区间和 sum >=target 时的最右侧(记为 right1 )能到哪里
-
我们既然已经找到 left1 开始的最优的区间,那么就可以大胆舍去 left1 。但是如果继续像方法一一样,重新开始统计第二个元素( left2 )往后的和,势必会有大量重复的计算(因为我们在求第一段区间的时候,已经算出很多元素的和了,这些和是可以在计算下次区间和的时候用上的)。
-
此时,right1 的作用就体现出来了,我们只需将 left1 这个值从 sum 中剔除。从 right1 这个元素开始,往后找满足 left2 元素的区间(此时 right1 也有可能是满足的,因为 left1 可能很小。sum 剔除 left1 之后,依旧满足大于等于 target )。这样我们就能省掉大量重复的计算
-
这样我们不仅能解决问题,而且效率也会大大提升
时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的。两者最多都往后移动 n 次。因此时间复杂度是 O(N)。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int len = INT_MAX;
int n = nums.size();
int sum = 0;
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;
}
};