209. 长度最小的子数组

Posted xiongxinxzy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了209. 长度最小的子数组相关的知识,希望对你有一定的参考价值。

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的连续子数组,返回 0。

 

示例:

输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的连续子数组。

 

进阶:

  • 如果你已经完成了 O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

================================================================================================================

感官: 第一次看到这个题觉得有点儿没有思路,不是属于一般的一看到就有思路的那种。题大致分为两类,一类是思维上取巧,一类是情况比较多需要妥善安排考虑,但是方法是一样的都是要先理清思路,思路不清后面写起来很累而且错误的机会也大大增加。

 

方法一:使用双滑窗

思路:

1)先找到一个大于或等于s的连续串,在这个过程中滑窗向前扩张。

2)收缩滑窗,目的是找到满足条件且最短的窗。

思路虽然很简单但是双滑窗是有套路的,不按照套路来很有可能出错。下来我就来理一下这个套路。

 1     int minSubArrayLen(int s, vector<int>& nums) {
 2         //用双滑窗的方法 这里尽量清晰和精简思路
 3         //开始滑
 4         int len = nums.size();
 5         int sums = 0; //表示目前窗内元素的和
 6         int ans_len = INT_MAX;
 7         int left = 0;
 8         int right = 0;
 9         while(!(right==len && sums<s)){
10             //第一步 确定双滑窗的区间  左闭右开
11             while(right<len && sums<s){ //没满足条件 开始扩张
12                 sums += nums[right];
13                 right++;
14             } 
15             //加完了  要么满足条件  要么加到低都不满足条件
16             if(sums>=s){
17                 int newlen = right-left;
18                 if(ans_len>newlen) ans_len = newlen;
19             }
20             //更新sums
21             sums -= nums[left];
22             left++;
23         }
24         if(ans_len == INT_MAX) return 0;
25         return ans_len;
26     }

相比之前的代码  这个版本的代码思路就清晰多了,在清晰的思路的指导下不管是写还是查错效率都会高很多。

具体清晰表现在:

1)确定了滑窗的区间,这里我采用的是左开右闭。得到的sums值是这个区间中的和。

2)  初始的时候,left = 0, right = 0; 这是一个空集,所以初始的sums=0,是对应的。

3)总结一下初始条件,区间是空区间,区间中的和为空。

4)第二个while循环开始扩张滑窗,扩张结束的条件是长度超过限制或者值够大。

5)计算新长度

6)收缩窗。

7)循环结束条件。1. 窗到底 且  窗中的值也小于目标值。

总结:整个思路很清晰,从初始化条件到最终求解都是一套整体的逻辑,不存在逻辑混乱的情况,所谓逻辑混乱就是本来是整体的逻辑现在却弄了好几套逻辑来表达,导致可能的情况太多,出错的机会就大。  但是还是存在一些无法完全说清的东西,本来也能说清,但是觉得没必要,这种东西交给临场逻辑去发挥。

 

方法二:二分查找

先说一下leetcode官网采取的二分查找法用的是当前节点向后看最短在哪儿。而我用的是向前,就是当前节点以及他的前面的节点能否组成一个最短的。

一样的道理还是要思路清晰。

先上代码:

 1     int minSubArrayLen(int s, vector<int>& nums) {
 2         //还是用二分查找的方法  再来一次
 3         int len = nums.size();
 4         if(len == 0) return 0;
 5         vector<int> sums(len+1, 0);
 6         int ans_len = INT_MAX;
 7         for(int i = 1; i<=len; i++){ //前缀和数组
 8             sums[i] = sums[i-1] + nums[i-1];
 9         }
10         for(int i = 1; i<=len; i++){
11             //到i为止的最小长度
12             int left = 0;
13             int right = i+1; //左闭右开区间
14             while(left+1<right){
15                 //计算差值  从左到右差值逐渐变小
16                 //目标  寻找从左到右最后一个大于等于s的位置
17                 int mid = (right+left)/2;
18                 int del = sums[i] - sums[mid];  //从 mid+1---i
19                 if(del>=s){   //
20                     left = mid;
21                 }else if(del<s){
22                     right = mid;
23                 }
24             }
25             if(sums[i] - sums[left]>=s){
26                 int bound = i-left;
27                 if(ans_len>bound) ans_len = bound;
28             }
29         }
30         if(ans_len == INT_MAX) return 0;
31         return ans_len;
32     }

二分查找的思路固然简单,但是我们还是应该明白一下几个问题:

1)二分查找的区间是什么意思,我采用的是左闭右开。

2)要查找什么? 比目标值小的最后一个? 比目标值大的最后一个? 其实可以分解为:小小小大大大  是查找最后一个小还是第一个大。

3)二分是否收敛, 换句话说是否会导致死循环。

再看看以上代码。

1)区间:左闭右开

2)查找目标,因为本题中是一个单调递减,要寻找大于等于目标值的第一个。

3)是否收敛:肯定会收敛,因为推出条件是left+1<right所以肯定收敛。

以上是关于209. 长度最小的子数组的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 209. 长度最小的子数组

LeetCode.209 长度最小的子数组

LeetCode#209-长度最小的子数组

Java算法 每日一题 编号209:长度最小的子数组

Java算法 每日一题 编号209:长度最小的子数组

LeetCode 209. 长度最小的子数组c++/java详细题解