Leetcode 862 和至少为 K 的最短子数组:前缀和+单调队列

文章内容

https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/

一、题目解析

给定整数数组 nums(元素可正可负)和整数 k,找出和 ≥ k 的最短非空连续子数组,返回其长度。不存在则返回 -1。

以 nums = [-28,81,-20,28,-29], k = 89 为例:子数组 [81,-20,28] 和为 89,长度 3,是满足条件的最短子数组。[81] 和 81 < 89,不满足。[81,-20,28,-29] 和 60 < 89,虽然更长也不满足。

由于存在负数,前缀和不再单调递增。普通滑动窗口失效——扩大窗口时和可能变小,缩小窗口时和可能变大,无法用双指针简单地维护。

二、前缀和 + 单调队列思路

2.1 前缀和转化

构建前缀和数组 preSum,其中 preSum[i] 表示 nums[0..i-1] 的和。子数组 nums[i..j] 的和 = preSum[j+1] - preSum[i]。问题转化为:找两个索引 x < y,使得 preSum[y] - preSum[x] ≥ k,且 y - x 最小。

2.2 单调递增队列

维护一个存索引的单调递增队列(按 preSum 值递增)。遍历 preSum,对每个位置 i:

去头(找答案):如果 preSum[i] - preSum[队头] ≥ k,说明队头作为起点已经满足条件。记录长度 i - 队头,弹出队头继续检查——因为队头匹配当前 i 已经是最短的,未来更远的 i 只会更长,队头已完成使命。

去尾(维护单调):如果 preSum[队尾] ≥ preSum[i],弹出队尾。队尾的 preSum 值更大且索引更靠前,属于"又老又弱"——未来元素与当前 i 配对,不仅更容易满足 preSum - preSum[i] ≥ k,而且距离更短。队尾被永久淘汰。

队列始终保持 preSum 值递增、索引递增。每个索引入队一次、出队一次。

前缀和+单调递增队列推演

2.3 完整推演

以 nums = [-28,81,-20,28,-29], k = 89 推一遍。preSum = [0, -28, 53, 33, 61, 32]。

i=0, preSum=0:入队。队列:[0]

i=1, preSum=-28:队头 0,-28-0=-28 < 89,不触发去头。队尾 preSum[0]=0 ≥ -28,弹 0。入队 1。队列:[1]

i=2, preSum=53:队头 1,53-(-28)=81 < 89。队尾 preSum[1]=-28 < 53,不入队头部操作。入队 2。队列:[1, 2]

i=3, preSum=33:队头 1,33-(-28)=61 < 89。队尾 preSum[2]=53 ≥ 33,弹 2。新队尾 preSum[1]=-28 < 33。入队 3。队列:[1, 3]

i=4, preSum=61:队头 1,61-(-28)=89 ≥ 89!ans=4-1=3。弹 1。新队头 3,61-33=28 < 89。队尾 preSum[3]=33 < 61。入队 4。队列:[3, 4]

i=5, preSum=32:队头 3,32-33=-1 < 89。队尾 preSum[4]=61 ≥ 32,弹 4。新队尾 preSum[3]=33 ≥ 32,弹 3。入队 5。队列:[5]

ans = 3。对应子数组 [81,-20,28]。

三、代码实现

java
class Solution {    public int shortestSubarray(int[] nums, int k) {        // 构建前缀和数组 preSum[i] = nums[0..i-1] 的和        int[] preSum = new int[nums.length + 1];        for (int i = 1; i <= nums.length; i++) {            preSum[i] = preSum[i - 1] + nums[i - 1];        }         // 单调递增队列,存前缀和索引,preSum 值递增        Deque<Integer> queue = new ArrayDeque<>();        int ans = Integer.MAX_VALUE;         for (int i = 0; i < preSum.length; i++) {            // 去头:队头作为起点已满足 preSum[i] - preSum[队头] >= k            while (!queue.isEmpty() && preSum[i] - preSum[queue.peekFirst()] >= k) {                ans = Math.min(ans, i - queue.peekFirst());                queue.pollFirst(); // 队头使命完成,弹出            }            // 去尾:队尾 preSum 更大且索引更靠前,被当前 i 永久淘汰            while (!queue.isEmpty() && preSum[queue.peekLast()] >= preSum[i]) {                queue.pollLast();            }            // 入队            queue.addLast(i);        }         return ans == Integer.MAX_VALUE ? -1 : ans;    }}

时间复杂度 O(n),每个索引至多入队一次、出队一次。空间复杂度 O(n),队列最多存 n 个索引。


本文由 AgentPlanFlow 生成

觉得不错?给作者点个赞吧

分类:题解

评论 (0)

无需注册,留言经站长审核后公开。

加载评论中…

Leetcode 862 和至少为 K 的最短子数组:前缀和+单调队列 · Yinchen 的博客