[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 }