一、题目简介
题目链接:209. 长度最小的子数组 - 力扣(LeetCode)
给定一个含有 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
进阶:
- 如果你已经实现
O(n)
时间复杂度的解法, 请尝试设计一个O(n log(n))
时间复杂度的解法。
二、解法
2.1 解法1:暴力解法
两层for循环遍历所有可能。
// 长度最小的子数组
// [1,1,1,3,3] target=7,返回:3
#include<iostream>
#include<vector>
class solution{public:// 两层for循环,暴力解法int minSubArrayLen(int target, std::vector<int>& nums){ int sum=0; // 子序列的数值之和 int result = INT32_MAX;for(int i=0;i<nums.size();i++){ // 设置子序列的起点为isum=0; // 这块的sum=0必须存在,否则程序运行结果不正确for(int j=i;j<nums.size();j++){ // 设置子序列终止位置为jsum=sum+nums[j]; if(sum>=target){ // 一旦发现子序列和超过了target,更新resultint subLength = j-i+1; // 取子序列的长度 result = result < subLength ? result : subLength; // 判断上一次的result是否小于此次subLength,若小于,则保留上一次的result;如果大于,则把此次的subLength的值赋给result,说明本轮找到的子序列长度要小于上一轮找到的子序列长度break;}}}return result == INT32_MAX ? 0 : result;}
};int main(){solution s1; // 对象实例化int target = 7; // 定义目标值std::vector<int> nums{1,2,3,4,5,6};int SubArraylen = s1.minSubArrayLen(target,nums);std::cout<<"子数组长度为:"<<SubArraylen<<std::endl;return 0;
}
给定数组[1,2,3,4,5,6],目标元素为target=7。
程序开始运行,进入函数内,执行流程为:
i=0,j=0,sum=0-->sum=1<target=7,不进入if判断语句。
i=0,j=1,sum=1-->sum=3<target=7,不进入if判断语句。
i=0,j=2,sum=3-->sum=6<target=7,不进入if判断语句。
i=0,j=3,sum=6-->sum=10>target=7,进入if判断语句,subLength=j-i+1=3-0+1=4,result=subLength=4。
最后执行break语句跳出内层for循环,开始执行外层for下一次循环,有i++,sum=0。
接下来执行流程为:
i=1,j=1,sum=0-->sum=2<target=7,不进入if判断语句。
i=1,j=2,sum=2-->sum=5<target=7,不进入if判断语句。
i=1,j=3,sum=5-->sum=9>target=7,进入if判断语句,subLength=j-i+1=3-1+1=3,result=subLength=3。
最后执行break语句跳出内层for循环,开始执行外层for下一次循环,有i++,sum=0。
接下来执行流程为:
i=2,j=2,sum=0-->sum=3<target=7,不进入if判断语句。
i=2,j=3,sum=3-->sum=7=target=7,进入if判断语句,subLength=j-i+1=3-2+1=2,result=subLength=2。
最后执行break语句跳出内层for循环,开始执行外层for下一次循环,有i++,sum=0。
。。。。。
依此类推,直至遍历完所有的可能。
该方法的时间复杂度与空间复杂度如下:
- 时间复杂度:O(n^2)
- 空间复杂度:O(1)
2.2 解法2:滑动窗口法
接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环为滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。首先要思考如果用一个for循环,那么应该表示滑动窗口的起始位置,还是终止位置。如果只用一个for循环来表示滑动窗口的起始位置,那么如何遍历剩下的终止位置?此时难免再次陷入 暴力解法的怪圈。所以 只用一个for循环,那么这个循环的索引,一定是表示滑动窗口的终止位置。那么问题来了,滑动窗口的起始位置如何移动呢?这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
最后找到 4,3 是最短距离。
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是满足其和 ≥ s 的长度最小的连续子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于窗口的起始位置如何移动,如图所示:
可以发现滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
// 长度最小的子数组
// [1,1,1,3,3] target=7,返回:3
#include<iostream>
#include<vector>
class solution{public:// 滑动窗口法【双指针法】int minSubArrayLen_slideWindow(int target, std::vector<int>& nums){int sum = 0; // 滑动窗口(子序列)数值之和int subLength = 0; // 定义滑动窗口的长度int i=0; // 定义滑动窗口起始位置int result = INT32_MAX;for(int j=0;j<nums.size();j++){sum = sum+nums[j];while(sum>=target){ // 一旦发现子序列和超过了target,更新resultsubLength = j-i+1;result = result<subLength? result:subLength;sum = sum-nums[i]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)i++;}}// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列return result == INT32_MAX ? 0:result;} };int main(){solution s1; // 对象实例化int target = 7; // 定义目标值std::vector<int> nums{1,2,3,4,5,6};int SubArraylen = s1.minSubArrayLen_slideWindow(target,nums);std::cout<<"子数组长度为:"<<SubArraylen<<std::endl;return 0;
}
给定数组[1,2,3,4,5,6],目标元素为target=7。
程序开始运行,进入函数内,执行流程为:
i=0,j=0,sum=0-->sum=1<target=7,不进入while循环。
i=0,j=1,sum=1-->sum=3<target=7,不进入while循环。
i=0,j=2,sum=3-->sum=6<target=7,不进入while循环。
i=0,j=3,sum=6-->sum=10>target=7,进入while循环。
进入while循环,
subLength=j-i+1=3-0+1=4,result=subLength=4,sum=sum-nums[i],则有:sum=10-->sum=9,随后执行i++语句后,i变为1,j仍然为3。
sum=9依然大于target,再次进入while循环,subLength=j-i+1=3-1+1=3,result=subLength=3,sum=sum-nums[i],则有:sum=9-->sum=7,随后执行i++语句后,i变为2,j还是3。
sum=7等于target,再次进入while循环,subLength=j-i+1=3-2+1=2,result=subLength=2,sum=sum-nums[i],则有:sum=7-->sum=4,随后执行i++语句后,i变为3。
此时sum为4,不符合while循环条件,退出while循环。
经过while循环之后,新的滑动窗口起始位置已经被找出,即i=3。接下来便可进行下一次for循环以更新j的值,随后按照上述流程执行即可。
该方法的时间复杂度和空间复杂度如下:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
一些录友会疑惑为什么时间复杂度是O(n)。
不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。
推荐算法解读视频:
拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili