力扣 每日一题 862. 和至少为 K 的最短子数组难度:困难,rating: 2306(前缀和+单调队列)
Posted nefu-ljw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣 每日一题 862. 和至少为 K 的最短子数组难度:困难,rating: 2306(前缀和+单调队列)相关的知识,希望对你有一定的参考价值。
题目链接
https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/
题目来源于:第91场周赛 Q4 rating: 2306
思路(简单版本:假如数组不包含负数)
我们首先做一下这题的简单版本【209. 长度最小的子数组】。
假如数组中不包括负数,全部为正整数。那么可以用双端队列(deque)直接维护前缀和(前缀和用于求指定左右端点的区间和)的单调递增区间,队列中的元素存储的是数组的下标。
具体做法是:遍历整个数组,每次先把队首弹出直到不满足区间和>=target的条件,同时记录满足区间和>=target的区间长度(队首到队尾的距离),然后加入当前下标到队尾。
class Solution
static const int N=1e5+10;
deque<int> q;
int sum[N];
public:
int minSubArrayLen(int target, vector<int>& nums)
int n=nums.size();
sum[0]=0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+nums[i-1];
int mi=N;
for(int i=0;i<=n;i++)
while(!q.empty()&&sum[i]-sum[q.front()]>=target)
// [q.front()+1,i]区间和>=target
mi=min(mi,i-q.front());
q.pop_front();
q.push_back(i);
return mi==N?0:mi;
;
这一过程可以简化为双指针,不用前缀和数组sum[]以及队列deque,从而降低空间复杂度到O(1),但时间复杂度仍为O(n)。
思路(本题:数组包含负数)
这题麻烦的地方在于数组中包含负数,使得前缀和不再单调递增。但我们可以想办法维护放入队列的下标 i 对应的前缀和 sum[i] 依然单调递增。
为了做到这一点,考虑在加入当前下标 i 到队列之前,弹出不影响后续答案的元素。
- 结论:假设 sum[i] 的前面有一些 sum[j] (j < i),满足 s u m [ j ] > = s u m [ i ] sum[j]>=sum[i] sum[j]>=sum[i],那么这些 j 被弹出并不会影响答案。
- 证明:因为后续在计算满足条件的最小区间长度时,这些 i,j 都是作为区间的左端点,假设右端点为 r,则 s u m [ r ] − s u m [ i ] > = s u m [ r ] − s u m [ j ] sum[r]-sum[i]>=sum[r]-sum[j] sum[r]−sum[i]>=sum[r]−sum[j]。那么如果 s u m [ r ] − s u m [ j ] > = k sum[r]-sum[j]>=k sum[r]−sum[j]>=k 则一定有 s u m [ r ] − s u m [ i ] > = k sum[r]-sum[i]>=k sum[r]−sum[i]>=k。这也就意味着,那些更靠前的 j 可以被 i 替代,i 不仅更靠后,能使得区间更短,而且还能使得区间和更大,所以就可以放心的弹出 j 了。
具体到代码实现中,就是弹出sum[q.back()]>=sum[i]
的队尾元素来维护单调队列。画一个例子方便理解:
上图中 i=2 时,将会弹出大于sum[2](即47)的队尾元素84。此时队列中元素为0, 2,对应的前缀和为0, 47。
代码
typedef long long ll;
class Solution
static const int N=1e5+10;
deque<int> q;
ll sum[N];
public:
int shortestSubarray(vector<int>& nums, int k)
int n=nums.size();
sum[0]=0;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+nums[i-1];
int mi=N;
for(int r=0;r<=n;r++)
while(!q.empty()&&sum[r]-sum[q.front()]>=k)
// 满足题目要求 [q.front()+1,r]区间和>=k
// 弹出队首,使得区间大小尽量缩短
mi=min(mi,r-q.front());
q.pop_front();
while(!q.empty()&&sum[q.back()]>=sum[r])
// 维护单调性,弹出大于等于当前元素的所有队尾
// 这样操作之后就能让[q.front,q.back]单调递增,那么在队尾之后再加入一个当前元素也单调递增
q.pop_back();
q.push_back(r);
return mi==N?-1:mi;
;
/*
[84,-37,32,40,95]
167
ans: 3
*/
以上是关于力扣 每日一题 862. 和至少为 K 的最短子数组难度:困难,rating: 2306(前缀和+单调队列)的主要内容,如果未能解决你的问题,请参考以下文章
862. 和至少为 K 的最短子数组(困难)-前缀和单调双端队列
⭐算法入门⭐《队列 - 单调队列》困难03 —— LeetCode 862. 和至少为 K 的最短子数组