三种办法,这里都说一下。
暴力
直接每一个字串全部加起来,然后算第一个大于 target 的下标减去刚开始加的下标,得到长度之后开始比较找最小。
class Solution { public: int minSubArrayLen(int target, vector<int>& nums) { int ans = INT_MAX; for(int i = 0;i<nums.size();i++) { int sum = 0; for(int j = i;j<nums.size();j++) { sum+=nums[j]; if(sum >= target) { ans = min(ans,j-i+1); } } } return ans == INT_MAX?0:ans; } };
其实你要是跟我说这个不超时的话我是不信的。
每次 LeetCode 针对这种暴力都是直接上很大一串代码的。
前缀和+二分
这里就可以用上前缀和了。
细说前缀和。
看起来好像很厉害的样子,实际上还算简单。
假设有一个数组 a ,这个数组里面是:2,3,1,2,4,3
那它的前缀和就是:0,2,5,6,8,12,15
第一个位置是 0 ,因为我们是从 1 开始加的
V[I] = V[I-1]+a[i-1];
有了这个前缀和,我们就可以继续往后写了。
-找位置-
C++里面有一个函数,叫做:lower_bound
JAVA里面叫做:Arrays.binarySearch
C#称之为:Array.BinarySearch
Python:bisect.bisect_left
其实它们都是一样的,就是找这一串数字里面第一个大于等于要找数字的位置。
返回的是一这个位置的迭代器,这里如果用上,如果熟悉的话会很简单,但是如果不熟悉,建议自己写一个二分。
其实也简单,找一个 >= tar 的元素,那就把判断条件改为 >= tar,其他的不变即可。
重新说回前缀和。
我们要找第一个元素它大于等于 tar 的区间长度,那我们就在前缀和里面找 >= tar 的元素即可。
假设:tar = 7
第一个元素是 2 ,它的前缀和是 0 ,那它要找的 tar就是 0+7 = 7,tar 就是它的前缀和+它本身, 区间应该是: 2,3,1,2,加起来结果是8,最后 2 的下标是 4 ,所以这段区间的长度就是 4 - 1 + 1。
这里的 -1 是因为是从第一个元素开始找的, +1 是因为别忘了这个元素也是要算在长度里面的。
第二个元素就跟第一个元素不一样了。
它是 3 ,它的前缀和是 2 ,所以它要找的 tar 是 2 + 7 = 9,为什么不一样?因为从这里开始算的话他是已经 +2 的结果(加了前面一个值),那它找的结果也要加前面的值,所以它找的是区间是:3,1,2,4,算下来也是 4 .
这里直接跳到 4 的位置。
4 的前缀和是 8 ,也就是说它要找的 tar 值是 8 + 7 = 15,那往后找找到结尾的 15,此时长度是 5 - 4 + 1 = 2,再将它与前面的比较即可。
思路说完了,来看一下代码怎么写。
第一种,调用 lower_bound:
class Solution { public: int minSubArrayLen(int target, vector<int>& nums) { vector<int> v(nums.size()+1,0); int ans = INT_MAX; for(int i = 1;i<=nums.size();i++) { v[i] = v[i-1] + nums[i-1]; } for(int i = 1;i<=nums.size();i++) { int tar = v[i-1]+target; auto pos = lower_bound(v.begin(),v.end(),tar); if(pos != v.end()) ans = min(ans, static_cast<int>((pos - v.begin()) - (i - 1))); } return ans == INT_MAX?0:ans; } };
static_cast<int> 就是强制类型转换,因为它是两个迭代器相减。
然后我们再自己写一个 lower_bound:
class Solution { public: int minSubArrayLen(int target, vector<int>& nums) { vector<int> v(nums.size()+1,0); int ans = INT_MAX; for(int i = 1;i<=nums.size();i++) { v[i] = v[i-1] + nums[i-1]; } for(int i = 1;i<=v.size();i++) { int tar = v[i-1]+target; int pos = my_lower_bound(v,tar); if(pos != -1) ans = min(ans,pos-i+1); } return ans == INT_MAX?0:ans; } int my_lower_bound(vector<int>& v,int tar) { int left = 0,right = v.size()-1; if(v[right] < tar) return -1; while(left < right) { int mid = (left+right)>>1; if(v[mid] >= tar) { right = mid; } else left = mid+1; } return right; } };
滑动窗口
直接上代码吧...这个就是一直 + ,+ 到大于 tar 的时候就可以算区间长度,然后 区间右移,继续 + ,其实思路跟前缀和有那么点像。
class Solution { public: int minSubArrayLen(int target, vector<int>& nums) { int start = 0,end = 0; int ans = INT_MAX; int sum = 0; while(end < nums.size()) { sum+=nums[end]; while(sum >= target){ ans = min(ans,end-start+1); sum -= nums[start]; start++; } end++; } return ans == INT_MAX ? 0 : ans; } };
拿这个举例:2,3,1,2,4,3
start 和 end 都从 0 开始,一直加到 大于 tar(7),区间是:2,3,1,2,end 在 2 的位置,然后end - start +1 注意 start 还在 0 没动,所以它的长度是 4 ,然后我们要做的就是将这个区间重新变为小于 tar 的状态,start - end 区间里面,end 可能很大,就让 start 一直往后移,每一次移动都减去start,直到它小于 tar,然后再继续加...
如果还看不懂也必要紧,这题 LeetCode 考点是 用 N*logN,也就是用第二种办法...
滑动窗口多看几遍,实在看不懂可以走读一下,加强自己的代码阅读能力。