209. 长度最小的子数组
Posted xiongxinxzy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了209. 长度最小的子数组相关的知识,希望对你有一定的参考价值。
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. 长度最小的子数组的主要内容,如果未能解决你的问题,请参考以下文章