首页 > 其他分享 >Leetcode第862题:和至少为K的最短子数组(Shortest Subarray with sum at least k)

Leetcode第862题:和至少为K的最短子数组(Shortest Subarray with sum at least k)

时间:2022-10-26 20:57:25浏览次数:48  
标签:前缀 nums int sum 862 least 短子 数组 移除

解题思路

前缀和

定义前缀和\(s[0] = 0\), \(s[i+1] = \displaystyle\sum\limits_{j=0}^i nums[j]\)。
例如 \(nums=[1,2,-1,2]\),对应的前缀和数组为\(s=[0,1,3,2,4]\)。

通过前缀和,可以将子数组和转换为两个前缀和的差,即
\(\sum\limits_{j=left}^{right} nums[j] = \sum\limits_{j=0}^{right} nums[j] - \sum\limits_{j=0}^{left-1} nums[j] = s[right+1] - s[left]\)

例如\(nums\)的子数组\([2,-1,2]\)的和就可以用\(s[4] - s[1] = 4 -1 = 3\)计算出来.
此时就可以通过枚举所有\(i>j\)且\(s[i] - s[j] \geq k\)的子数组\([j,i)\),取其中最小的\(i-j\)作为答案.

单调队列

枚举所有前缀和子数组的时间复杂度是\(O(n^2)\),使用某个数据结构维护遍历过的\(s[i]\),及时移除无用的\(s[i]\).

第一,当遍历到\(s[i]\)时,如果左边存在某个\(s[j]\),满足\(s[i] - s[j] \geq k\),那么无论\(s[i]\)右边的数字大小,都不会把\(j\)当作子数组的左端点,然后得到一个比\(i - j\)更短的子数组.因此可以将\(s[j]\)移除.

第二,如果\(s[i] \leq s[j]\),假如后续有数字\(x\)能和\(s[j]\)组成满足要求的子数组,即\(x - s[j] \geq k\),那么必然也有\(x - s[i] \geq k\),由于从\(s[i]\)到\(x\)的子数组更短,因此可以将\(s[j]\)移除.

做完这两个优化后,再把\(s[i]\)加到这个数据结构中.第二个优化保证了数据结构中的\(s[i]\)会形成一个递增的序列,因此第一个优化移除的是序列最左侧的若干元素,第二个优化移除的是最右侧的若干元素.因此该数据结构满足移除最左端元素和最右端元素,以及在最右端添加元素,故选用双端队列.元素保持单调递增的双端队列也成为单调队列.

核心代码如下:

 int shortestSubarray(vector<int> &nums, int k) {
        int n = nums.size(), ans = n + 1;
        long s[n + 1];
        s[0] = 0L;
        for (int i = 0; i < n; ++i)
            s[i + 1] = s[i] + nums[i]; // 计算前缀和
        deque<int> q;
        for (int i = 0; i <= n; ++i) {
            long cur_s = s[i];
            while (!q.empty() && cur_s - s[q.front()] >= k) {
                ans = min(ans, i - q.front());
                q.pop_front(); // 优化一
            }
            while (!q.empty() && s[q.back()] >= cur_s)
                q.pop_back(); // 优化二
            q.push_back(i);
        }
        return ans > n ? -1 : ans;
    }

标签:前缀,nums,int,sum,862,least,短子,数组,移除
From: https://www.cnblogs.com/hql5/p/16829981.html

相关文章