[LeetCode] 862. Shortest Subarray with Sum at Least K

Given an integer array nums and an integer k, return the length of the shortest non-empty subarray of nums with a sum of at least k. If there is no such subarray, return -1.

A subarray is a contiguous part of an array. 

Example 1:

Input: nums = [1], k = 1
Output: 1

Example 2:

Input: nums = [1,2], k = 4
Output: -1

Example 3:

Input: nums = [2,-1,2], k = 3
Output: 3

Constraints:

  • 1 <= nums.length <= 105
  • -105 <= nums[i] <= 105
  • 1 <= k <= 109

和至少为 K 的最短子数组。

给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。

子数组 是数组中 连续 的一部分。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题很好,我直接介绍最优解,思路是前缀和 + 单调队列。本题同 LintCode 1507题

注意题目让你找的是子数组,同时要知道子数组的数组和,那么第一反应就应该是先用前缀和去记录整个数组的前缀和,然后再找机会去找到满足题意的子数组。同时这里我们注意题目给的条件,1 <= k <= 109,说明 K 是一个正数,又由于我们找的是前缀和数组里面一段 sum = K 的子数组,那么 sum 也必须大于 0 。这一段前缀和子数组最好是首个元素 presum[first] 尽可能小 && 最后一个元素 presum[last] 尽可能大。由这个推断出的条件我们可以尝试使用单调队列,并且这是一个单调递增队列。这里如果想不清楚你可以举个反例,单调递减的队列肯定不对,因为如果满足题意的子数组这一段的前缀和是单调递减的,那么说明这一段子数组的和一定是负的,就会和 K > 0 冲突。同时注意单调栈/单调队列的题,我们放入栈内的元素一般都是数组的下标而不是值,起码 leetcode 上的题都是这样。

所以这里我们先开一个数组记录 input 数组的前缀和,记为 int[] presum。接着我们开始遍历这个 presum 数组。对于每个遇到的 presum[i],如果当前队列不为空,我们去看一下当前这个 index i 的前缀和 presum[i] 是否 <= 此时队列最右边元素指向的前缀和。这里我们是为单调递增的那一段找一个可能的最低点。如果一直满足这个条件,presum[i] <= presum[queue.peekLast()],我们就把 queue 最右边那个index 一直 remove。这样当操作停止的时候,queue 最右边那个 index 指向的前缀和是最小的。

接下来我们找一个可能的最高点。对于当前 index 指向的前缀和 presum[i],我们去看一下队列最左边那个 index 指向的前缀和是否满足 presum[queue.peekFirst()] + k >= presum[i]。如果满足这个公式,则我们就找到了一个可行解,这个解的长度是 i - queue.removeFirst()。此时就可以把队列最左边那个 index 去掉了,因为已经检查过了。

时间O(n^2) - worst case

空间O(n) - 前缀和数组

Java实现

 1 class Solution {
 2     public int shortestSubarray(int[] nums, int k) {
 3         int len = nums.length;
 4         long[] presum = new long[len + 1];
 5         for (int i = 0; i < len; i++) {
 6             presum[i + 1] = presum[i] + (long) nums[i];
 7         }
 8         
 9         int res = Integer.MAX_VALUE;
10         Deque<Integer> queue = new LinkedList<>();
11         for (int i = 0; i < presum.length; i++) {
12             // find a lower point if possible
13             while (!queue.isEmpty() && presum[i] <= presum[queue.peekLast()]) {
14                 queue.removeLast();
15             }
16             // check if the current number >= the left most in queue + k
17             while (!queue.isEmpty() && presum[i] >= presum[queue.peekFirst()] + k) {
18                 res = Math.min(res, i - queue.removeFirst());
19             }
20             queue.addLast(i);
21         }
22         return res == Integer.MAX_VALUE ? -1 : res;
23     }
24 }

 

LeetCode 题目总结

posted @ 2022-06-23 13:56  CNoodle  阅读(75)  评论(0编辑  收藏  举报