Leetcode——209 长度最小的子数组
题目描述
-
给定一个含有
n
个正整数的数组和一个正整数target
。找出该数组中满足其总和大于等于
target
的长度最小的 子数组[numsl, numsl+1, ..., numsr-1, numsr]
,并返回其长度**。**如果不存在符合条件的子数组,返回0
。
题解1:前缀和+二分
暴力解法就是枚举所有可能的i和j,在此情况下思考,是否可以把j的求法优化一下呢?
如果让每个i作为终点,能快速找到j(j≤ij \leq ij≤i)使得j - i + 1(即长度)最小且nums[j…i]的和满足题目条件。
换句话说,j是有单调性的,如果满足条件,那么让j往i走,否则往远离i走。
现在的问题就是如何编写二分中的check函数,使得能够快速检查这段区间和是否满足条件呢?
很自然的想到前缀和,如果预处理sumisum_isumi表示nums1nums_{1}nums1到numsinums_inumsi的和,那么求nums[l…r]的和的计算公式为:
suml,r=sum[r]−sum[l−1]
sum_{l,r} = sum[r] - sum[l - 1]
suml,r=sum[r]−sum[l−1]
这样只需要花费一定的空间就可以节省check时求和所需要的时间。
PS:这种方式最好数组从1开始存储,若从0的话比较麻烦。
代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
if(nums.size() == 0) return 0;
vector<int>v;
v.push_back(0);
int n = nums.size();
for(int i = 0;i < n;i ++) {
v.push_back(nums[i]);
}
vector <int> sum;
sum.push_back(0);
for(int i = 1;i <= n;i ++) {
sum.push_back(sum[i - 1] + v[i]);
}
int ans = INT_MAX;
for(int i = 1;i <= n;i ++) {
int l = 1;
int r = i;
while(l < r) {
int mid = l + r + 1 >> 1;
if(sum[i] - sum[mid - 1] >= target) l = mid;
else r = mid - 1;
}
if(sum[i] - sum[l - 1] >= target) ans = min(ans,i - l + 1);
}
if(ans > INT_MAX / 2) return 0;
else return ans;
}
};
时间复杂度O(nlogn)O(nlogn)O(nlogn)
空间复杂度O(n)O(n)O(n)
题解2 利用数组性质——滑动窗口
其实从解法1来看,二分求解一个j的方法还是太麻烦了,为什么呢?
举个例子,比如我目前求出了区间[j,i]是满足题意的,那么对于下一个i(i = i + 1),区间内的和肯定会变大,但是这个变大到底影响多少呢?如果我尝试收紧左端,这个变大是否能抵消掉收紧所带来的影响呢?
比如:[1,2,3]再来个4,target=6的情况下,只需要2就可以;当区间来到[1,3]时,加入4,尝试删除1和2发现还是满足区间和大于等于target
那么我只需要从数组开头记录一个j,每次枚举一个i,尝试收束左边界即可,区间和只需要加加减减即可,代码如下:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
if(nums.size() == 0) return 0;
int n = nums.size();
int ans = INT_MAX;
int head = 0;
int sum = 0;
for(int i = 0;i < n;i ++) {
sum += nums[i];
while(sum >= target) {
ans = min(ans,i - head + 1);
sum -= nums[head];
head ++;
}
}
return ans > INT_MAX / 2 ? 0 : ans;
}
};
时间复杂度O(n)
空间复杂度O(1)