|
| 1 | +# [0862. 和至少为 K 的最短子数组](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) |
| 2 | + |
| 3 | +- 标签:队列、数组、二分查找、前缀和、滑动窗口、单调队列、堆(优先队列) |
| 4 | +- 难度:困难 |
| 5 | + |
| 6 | +## 题目链接 |
| 7 | + |
| 8 | +- [0862. 和至少为 K 的最短子数组 - 力扣](https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/) |
| 9 | + |
| 10 | +## 题目大意 |
| 11 | + |
| 12 | +**描述**:给定一个整数数组 $nums$ 和一个整数 $k$。 |
| 13 | + |
| 14 | +**要求**:找出 $nums$ 中和至少为 $k$ 的最短非空子数组,并返回该子数组的长度。如果不存在这样的子数组,返回 $-1$。 |
| 15 | + |
| 16 | +**说明**: |
| 17 | + |
| 18 | +- **子数组**:数组中连续的一部分。 |
| 19 | +- 1ドル \le nums.length \le 10^5$。 |
| 20 | +- $-10^5 \le nums[i] \le 10^5$。 |
| 21 | +- 1ドル \le k \le 10^9$。 |
| 22 | + |
| 23 | +**示例**: |
| 24 | + |
| 25 | +- 示例 1: |
| 26 | + |
| 27 | +```python |
| 28 | +输入:nums = [1], k = 1 |
| 29 | +输出:1 |
| 30 | +``` |
| 31 | + |
| 32 | +- 示例 2: |
| 33 | + |
| 34 | +```python |
| 35 | +输入:nums = [1,2], k = 4 |
| 36 | +输出:-1 |
| 37 | +``` |
| 38 | + |
| 39 | +## 解题思路 |
| 40 | + |
| 41 | +### 思路 1:前缀和 + 单调队列 |
| 42 | + |
| 43 | +题目要求得到满足和至少为 $k$ 的子数组的最短长度。 |
| 44 | + |
| 45 | +先来考虑暴力做法。如果使用两重循环分别遍历子数组的开始和结束位置,则可以直接求出所有满足条件的子数组,以及对应长度。但是这种做法的时间复杂度为 $O(n^2)$。我们需要对其进行优化。 |
| 46 | + |
| 47 | +#### 1. 前缀和优化 |
| 48 | + |
| 49 | +首先对于子数组和,我们可以使用「前缀和」的方式,方便快速的得到某个子数组的和。 |
| 50 | + |
| 51 | +对于区间 $[left, right],ドル通过 $pre\underline{}sum[right + 1] - prefix\underline{}cnts[left]$ 即可快速求解出区间 $[left, right]$ 的子数组和。 |
| 52 | + |
| 53 | +此时问题就转变为:是否能找到满足 $i > j$ 且 $pre\underline{}sum[i] - pre\underline{}sum[j] \ge k$ 两个条件的子数组 $[j, i)$?如果能找到,则找出 $i - j$ 差值最小的作为答案。 |
| 54 | + |
| 55 | +#### 2. 单调队列优化 |
| 56 | + |
| 57 | +对于区间 $[j, i)$ 来说,我们应该尽可能的减少不成立的区间枚举。 |
| 58 | + |
| 59 | +1. 对于某个区间 $[j, i)$ 来说,如果 $pre\underline{}sum[i] - pre\underline{}sum[j] \ge k,ドル那么大于 $i$ 的索引值就不用再进行枚举了,不可能比 $i - j$ 的差值更优了。此时我们应该尽可能的向右移动 $j,ドル从而使得 $i - j$ 更小。 |
| 60 | +2. 对于某个区间 $[j, i)$ 来说,如果 $pre\underline{}sum[j] \ge pre\underline{}sum[i],ドル对于任何大于等于 $i$ 的索引值 $r$ 来说,$pre\underline{}sum[r] - pre\underline{}sum[i]$ 一定比 $pre\underline{}sum[i] - pre\underline{}sum[j]$ 更小且长度更小。此时 $pre\underline{}sum[j]$ 可以直接忽略掉。 |
| 61 | + |
| 62 | +因此,我们可以使用单调队列来保存单调递增的 $pre\underline{}sum[x]$ 值的下标。 |
| 63 | + |
| 64 | +对于每一个位置 $i$ 我们可以判断其之前存入在单调队列中的 $pre\underline{}sum[j]$ 值,如果 $pre\underline{}sum[i] - pre\underline{}sum[j] \ge k,ドル则更新答案,并将 $j$ 从队头位置弹出。直到 $pre\underline{}sum[i] - pre\underline{}sum[j] < k$ 时为止。 |
| 65 | + |
| 66 | +如果队尾 $pre\underline{}sum[j] \ge pre\underline{}sum[i],ドル那么 $$ |
| 67 | + |
| 68 | +使用一重循环遍历 $i,ドル对于 $pre\underline{}sum[i],ドル我们希望使用某个数据结构,能够使得在满足 $pre\underline{}sum[i] - pre\underline{}sum[j] \ge k$ 当前前提下,能够尽可能的向右移动 $j,ドル从而使得 $i - j$ 最小。 |
| 69 | + |
| 70 | +### 思路 1:代码 |
| 71 | + |
| 72 | +```Python |
| 73 | +class Solution: |
| 74 | + def shortestSubarray(self, nums: List[int], k: int) -> int: |
| 75 | + size = len(nums) |
| 76 | + |
| 77 | + pre_sum = [0 for _ in range(size + 1)] |
| 78 | + for i in range(size): |
| 79 | + pre_sum[i + 1] = pre_sum[i] + nums[i] |
| 80 | + |
| 81 | + ans = float('inf') |
| 82 | + queue = collections.deque() |
| 83 | + |
| 84 | + for i in range(size + 1): |
| 85 | + # 优化 1 |
| 86 | + while queue and pre_sum[i] - pre_sum[queue[0]] >= k: |
| 87 | + ans = min(ans, i - queue.popleft()) |
| 88 | + # 优化 2 |
| 89 | + while queue and pre_sum[queue[-1]] >= pre_sum[i]: |
| 90 | + queue.pop() |
| 91 | + queue.append(i) |
| 92 | + |
| 93 | + if ans == float('inf'): |
| 94 | + return -1 |
| 95 | + return ans |
| 96 | +``` |
| 97 | + |
| 98 | +### 思路 1:复杂度分析 |
| 99 | + |
| 100 | +- **时间复杂度**:$O(n),ドル其中 $n$ 为数组 $nums$ 的长度。 |
| 101 | +- **空间复杂度**:$O(n)$。 |
| 102 | + |
0 commit comments