题目
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例
输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
思路一
直接暴力解法,枚举出所有的子数组,然后判断符合条件的,并从符合条件的子数组中找出长度最短的。故可以通过双层for循环来实现,其中外层循环控制子数组的起始位置,内层循环控制子数组的终止位置。通过不同起始位置和终止位置组合出子数组,即可枚举出所有的子数组。
思路二
因为在暴力解法的实现中,会枚举出一些无效的子数组(列如:假设第一次外层循环子数组累加正好等于目标值,呢在进行第二次外层循环的时候,起始位置加1固定的情况下,终止位置在变大的过程中,呢就会有一定的无效子数组被枚举出来),所以我i们可以在此基础上想办法减少甚至消除这种无效子数组的,可以使用滑动窗口的方法(类似双指针),就是不断地动态的调节起始位置和终止位置,但是区别于暴力求解调整位置的方式。
关键点
-
滑动窗口中,当窗口值符合要求时,我们才移动窗口的起始位置。而终止位置是始终随着for循环不断移动的。
-
for循环里面的循环变量是终止位置 ,不是起始位置,若是起始位置的话,呢就和暴力求解的思想一样了。
c++代码实现1:暴力解法
#include <iostream>
#include <vector>
using namespace std;
// 方法一暴力解法
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums ) {
int result = INT32_MAX;
//带符号int型的最大值(c++中定义的一个宏常量),result作为返回的子数组长度的结果
int sum = 0;
//子数组元素之和
int subLength = 0;
//当前满足的子数组的长度
//外层循环控制子数组的起始位置(起始位置为i)并且可以枚举出终止位置为j的所有子数组
for (int i = 0; i < nums.size(); i++) {
sum = 0;
//初始子数组元素之和为0
// 内存循环控制子数组的终止位置 (终止位置为j)并且可以枚举出起始位置为i情况下的所有子数组
for (int j = i; j < nums.size(); j++) {
sum += nums[j];
//累加子数组的元素
//判断:一旦当前子序列和大于等于s,就可以更新一次result的值(即寻找满足条件的最小的子数组)
if(sum >= s) {
subLength = j - i + 1;
//当前满足条件的子数组的长度
result = result < subLength ? result : subLength;
//因为是找满足条件的最短子数组,所以一旦符合条件就可以跳出循环,继续循环下去的子数组长度只会越来越长。
break;
}
}
}
//如果result没有被更新过,代表不存在符合条件的子数组就返回0
return result == INT32_MAX ? 0 : result;
}
};
int main() {
//测试一下
// 示例:
// 输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
//声明一个vector容器
vector<int> nums = {2, 3, 1, 2, 4, 3};
Solution s1;
cout << s1.minSubArrayLen(7, nums);
return 0;
}
- 时间复杂度
- 空间复杂度
c++代码实现2:滑动窗口
#include <iostream>
#include <vector>
using namespace std;
//方法二滑动窗口
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
//带符号int型的最大值(c++中定义的一个宏常量),result作为返回的子数组长度的结果
int result = INT32_MAX;
int sum = 0;
//滑动窗口数值之和(子数组元素之和)
int i = 0;
//滑动窗口的起始位置(后面会动态的改变起始位置的值)
int subLength = 0;
//滑动窗口的长度(当前满足的子数组的长度)
// 类似双指针的实现方式,通过一层for循环实现双层for循环的功能,这里的j指的是滑动窗口的!!!终止位置
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
//滑动窗口元素累加
//注意这里的while
while (sum >= s) {
subLength = j - i + 1;
//取当前滑动窗口的长度
result = result < subLength ? result : subLength;
//更新reslut的值
//注意;这里i是后自增,在当前满足的滑动窗口的基础上,进一步缩小长度(通过改变起始位置的值),并进行判断看缩小后的长度是否符合要求。
sum -= nums[i++];
}
}
//如果result没有被更新过,代表不存在符合条件的子数组就返回0
return result == INT32_MAX ? 0 : result;
}
};
int main() {
//测试一下
// 示例:
// 输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
//声明一个vector容器
vector<int> nums = {2, 3, 1, 2, 4, 3};
Solution s1;
cout << s1.minSubArrayLen(7, nums);
return 0;
}
- 时间复杂度
- 空间复杂度
总结
-
很多双层循环的部分都可以通过双指针的思想去降低时间复杂度。
-
滑动窗口的关键就在于根据子数组的大小,不断的调节起始位置,从而减少无效子数组,来降低时间复杂度。