209.长度最小的子数组

302 阅读3分钟

题目

给定一个含有 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;
 }
  • 时间复杂度 O(n2)O(n^2)
  • 空间复杂度 O(1)O(1)

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;
 }
  • 时间复杂度 O(n)O(n)
  • 空间复杂度 O(1)O(1)

总结

  • 很多双层循环的部分都可以通过双指针的思想去降低时间复杂度。

  • 滑动窗口的关键就在于根据子数组的大小,不断的调节起始位置,从而减少无效子数组,来降低时间复杂度。