力扣 每日一题 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 的最短子数组

LeetCode 862. 和至少为 K 的最短子数组

862. 和至少为 K 的最短子数组(困难)-前缀和单调双端队列

⭐算法入门⭐《队列 - 单调队列》困难03 —— LeetCode 862. 和至少为 K 的最短子数组

LeetCode 862 和至少为K的最短子数组[前缀和 双端队列] HERODING的LeetCode之路

和至少为K的最短子数组