题目
- 给定一个数组,若有子数组之和 ≥ k,返回子数组长度的最小值
- 若不存在这样的子数组,返回 -1
思路
-
l ≤ r, left ≤ right
-
子数组 [l, r] 的和,为 presum[r] - presum[l-1]
-
满足条件的子数组为 presum[r] - presum[l] ≥ k,更新满足条件的子数组长度的最小值,r - l + 1
-
按照顺序遍历 presum[r],核心是找到满足 presum[l] ≤ presum[r] - k,并且 l 要最大;或者反过来,按照顺序遍历 presum[l],找到满足条件的最小 right index
-
单调递增队列可以完美解决该问题,单调队列记录递增的 presum 值对应的最大 index;
-
假设当前遍历至 index,
- 若 queue 的头 presum[queue.front()] ≤ presum[index] - k, 那么更新满足条件的长度;后续的 index 若有满足条件的,那么必定比当前的数组长度长,queue.pop_front 可以不再参与计算;
- 若 queue 的尾 presum[index] ≤ presum[queue.back()], 因为要最小长度的子数组,只需要记录当前 index 和 presum[index] 即可;若后续的 presum[index + x] - presum[queue.back()] ≥ k,presum[index] 比 presum[queue.back()] 小,更可以满足该条件,子数组长度也更小,queue.back 可以不再参与计算;
- 反之,直接记录当前 presum 的 index,加入到队列尾部;
要点
- monotonic-queue 单调队列的应用场景为单调栈和滑动窗口的最值 ,时间复杂度为 o(n)
- 题目 hard 的点在于,可以把问题转化为单调队列
代码
class Solution {
public:
int shortestSubarray(vector<int>& nums, int k) {
vector<long long> presum;
presum.push_back(0);
int ans = -1;
deque<int> dq;
dq.push_back(0);
for (int i = 0; i < nums.size(); i++) {
presum.push_back(presum[i] + nums[i]);
// check front
while (!dq.empty() && presum[i+1] - presum[dq.front()] >= k) {
if (ans == -1) ans = i + 1 - dq.front();
else ans = min(ans, i + 1 - dq.front());
dq.pop_front();
}
// update dq
while (!dq.empty() && presum[dq.back()] >= presum[i+1]) {
dq.pop_back();
}
dq.push_back(i+1);
}
return ans;
}
};