给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。
子数组 是数组中 连续 的一部分。
示例 1:
输入:nums = [1], k = 1
输出:1
示例 2:
输入:nums = [1,2], k = 4
输出:-1
示例 3:
输入:nums = [2,-1,2], k = 3
输出:3
提示:
1 <= nums.length <= 105
-105 <= nums[i] <= 105
1 <= k <= 109
思路:
1.暴力法(会超时)
显然这里需要用到前缀和数组,数组s[i]代表nums的前i个元素的和,s[j]-s[i]则代表从i到j的这一区间的和。
然后第一层循环从0开始,下一层循环从上一层的下一个位置开始,找满足s[j]-s[i]>=k条件的点,记住它们的长度,重复这个过程,知道找到最小值。
2.单调队列
遍历 s数组,访问过的前缀和先暂存在队列 q 中。根据前缀和数组的性质,后访问到的某个前缀和s[j] 减去之前访问到的某个前缀和,即为 nums 中某段子数组的和。因此,每次访问到某个前缀和 s[j] 时,可以用它减去队列 q中所有已经访问过的前缀和。当 q 中的某个前缀和 s[i],第一次出现 s[j]−s[i]≥k 时,就找到了以它i为起点的最短子数组的长度 j-i。此时,可以将它从 q中移除,后续即使还有以它为起点的满足条件的子数组,长度也会大于当前的长度。当一个前缀和 s[j] 试减完 q中的元素时,需要将它也放入 q中。将它放入 q前, q中可能存在比 s[j]大的元素,而这些元素和s[j] 一样,只能作为再后续访问到的某个前缀和s[h] 的减数。而作为减数时,更大的值只会让不等式 s[h]−s[i]≥k 更难满足。即使都满足,后访问到的值也可以带来更短的长度。 因此,在把 s[j] 放入 q时,需要将 q中大于等于s[j] 的值也都移除。
队列q中暂存的元素是的下标,对应下标的前缀和严格单调递增。
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
int n = nums.size();
vector<long> preSumArr(n + 1);
for (int i = 0; i < n; i++) {
preSumArr[i + 1] = preSumArr[i] + nums[i];
}
int res = n + 1;
deque<int> qu;
for (int i = 0; i <= n; i++) {
long curSum = preSumArr[i];
while (!qu.empty() && curSum - preSumArr[qu.front()] >= k) {
res = min(res, i - qu.front());
qu.pop_front();
}
while (!qu.empty() && preSumArr[qu.back()] >= curSum) {
qu.pop_back();
}
qu.push_back(i);
}
return res < n + 1 ? res : -1;
}
};