解题思路:
1.自己写的时间复杂度O(n*logn)
首先想到了前缀和,一个辅助的数组sum记录前缀和。
①在遍历原数组时判断是否有一个元素值 >= target,如果有则返回1,这肯定是最小的子数组了。
②判断最后一个元素的前缀和 ,如果sum[nums.size()+1] < target ,前缀和的最大值都没有 > target,直接返回 0。
③先找到前缀和 >= target 的下标 i ,然后从 i 开始向后找在 nums[0] ~ nums[i]区间内的最小区间使得 sum[i] - sum[j] >= target 。
代码:
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int size = nums.size();
vector<int> sum(size + 1, 0);
for (int i = 0; i < size; ++i)
{
if (nums[i] >= target)//存在一个数 >= target
return 1;
sum[i + 1] = nums[i] + sum[i];
}
if (sum[size] < target) //所有数的总和都没有 > target
return 0;
int minlength = INT_MAX;
int i = 1;
for (; i <= size; ++i)
{
if (sum[i] == target) //先找到 > target的下标
{
minlength = i;
}
else if (sum[i] > target)
{
break;
}
}
for (; i <= size; ++i)
{
for (int j = i - 2; j >= 0; --j)//从这个前缀和向前找最小区间
{
int _target = sum[i] - sum[j]; //区间的差值
if (_target >= target)//只要满足了条件一定是这个区间的最小子数组
{
minlength = min(minlength, i - j);
break;
}
}
}
return minlength;
}
};
2.优化 - 二分查找
我们申请一个临时数组 sums,其中 sums[i] 表示的是原数组 nums 前 i 个元素的和,题中说了 “给定一个含有 n 个 正整数 的数组”,既然是正整数,那么相加的和会越来越大,也就是sums数组中的元素是递增的。我们只需要找到 sums[k]-sums[j]>=s,那么 k-j 就是满足的连续子数组,但不一定是最小的,所以我们要继续找,直到找到最小的为止。求 sums[k]-sums[j]>=s 我们可以求 sums[j]+s<=sums[k],那这样就好办了,因为数组sums中的元素是递增的,也就是排序的,我们只需要求出 sum[j]+s 的值,然后使用二分法查找即可找到这个 k即可。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums)
{
int size = nums.size();
vector<int> sum(size + 1, 0);
for (int i = 0; i < size; ++i)
{
if (nums[i] >= target)//存在一个数 >= target
return 1;
sum[i + 1] = nums[i] + sum[i];
}
if (sum[size] < target) //所有数的总和都没有 > target
return 0;
//二分
int minlength = INT_MAX;
for(int i = 0; i < size-1 ;++i) //最后两个元素不用判断
{
int _target = target+sum[i];
if(_target > sum[size])//预期值> 前缀和的最大值不合理,跳出循环
break;
int left = i+1;
int right = size;
while(left < right)
{
int mid = (left+right) >> 1;
if(sum[mid] >= _target)
{
right = mid;
}
else
{
left = mid+1;
}
}
//当while停下来的时候,right指向的就是使区间最小的前缀和。
minlength = min(minlength , right-i);
}
return minlength;
}
};
3.滑动窗口 O(n)
定义两个下标left,right初始化为0 ,窗口值windows_nums = 0 ,
①刚开始 right 向右走,窗口值+=nums[right] , 直到windows_num > target ;
②此时left 开始向右移只要windows_sum >= target , len = right - left //新的子数组长度 ; res = min(res, len); //更新窗口大小 windows_sum -= nums[left];//更新窗口状态
③在right < num.size() 情况下,重复①② 。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int left = 0, right = 0, res = INT_MAX, len = 0;
int windows_sum = 0;
for(int i= 0 ; i < nums.size() ;++i) //小优化
{
if(nums[i] >= target)
return 1;
}
while(right < nums.size())//窗口区间为[left, right)
{
while(right < nums.size() && windows_sum < target) //增大窗口 ->右边先走
{
windows_sum += nums[right];//更新窗口状态
right++;
}
while(windows_sum >= target)//收缩窗口
{
len = right - left;
res = min(res, len); //更新窗口大小
windows_sum -= nums[left];//更新窗口状态
left++;
}
}
return res == INT_MAX? 0:res;
}
};