1. 例题
560. 和为 K 的子数组
862. 和至少为 K 的最短子数组
3097. 或值至少为 K 的最短子数组 II
2. 思路
三者都用到了动态规划的思想,但需要结合不同的数据结构进行处理。
2.1 LC560 和为 K 的子数组个数
做法:前缀和 + 哈希表
我们需要统计数组中和为 k 的子数组个数,可以先求前缀和数组 prefix(包含自身),然后推导一下递推式。
要找 [j, … ,i] 的和为k,其实就是找 prefix[i] - prefix[j - 1] = k
就是要找有多少个 prefix[j - 1] = prefix[i] - k
因此用一个哈希表来记录所有 prefix 的个数即可,对应代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public int subarraySum(int[] nums, int k) { int n = nums.length; HashMap<Integer, Integer> map = new HashMap<>(); map.put(0, 1); int res = 0; int sum = 0; for (int i = 0; i < n; i++) { sum += nums[i]; if (map.containsKey(sum - k)) { res += map.get(sum - k); } map.put(sum, map.getOrDefault(sum, 0) + 1); } return res; } }
|
2.2 LC862 和至少为 k 的最短子数组长度
在 LC560 上进行改编,变成了:和至少为 k、求最短子数组长度
不变的是要求前缀和,但要求最短子数组,可以使用一个单调的双端队列进行记录
当前前缀和和 - 队列第一个元素(下标)对应的前缀和 >= k 时,就更新一下长度,然后将这个元素出队。这个过程要保证队列中下标对应的前缀和是从小到大的。
Tips:求数组中 第一个符合xxx条件 的元素的位置,就要联想到单调栈、单调队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Solution { public int shortestSubarray(int[] nums, int k) { int n = nums.length;
long[] preSum = new long[n + 1]; for (int i = 0; i < n; i++) { preSum[i + 1] = preSum[i] + nums[i]; }
Deque<Integer> dq = new ArrayDeque<Integer>(); int minLength = Integer.MAX_VALUE; for (int i = 0; i <= n; i++) { long curSum = preSum[i]; while (!dq.isEmpty() && curSum - preSum[dq.peekFirst()] >= k) { minLength = Math.min(minLength, i - dq.pollFirst()); } while (!dq.isEmpty() && curSum <= preSum[dq.peekLast()]) { dq.pollLast(); } dq.offerLast(i); } minLength = minLength == Integer.MAX_VALUE ? -1 : minLength; return minLength; } }
|
2.3 LC3097 或值至少为 k 的最短子数组长度
LC560 和 LC862 都可以用前缀和 + 数据结构解决问题,该题没办法再用前缀xxx,因为前缀和相减可以得到某一段子数组的区间和,但或运算不行,也没有 “前缀或” 这种概念。
因此需要另外一种记录方式,用一个数组记录每个位置到当前位置的 OR 值,并标记可以取的左端点的最大值。
如果 OR 值重复,则需要去重,留下左端点较大的那个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| class Solution { public int minimumSubarrayLength(int[] nums, int k) { int minLength = Integer.MAX_VALUE;
List<int[]> ors = new ArrayList<>();
for (int i = 0; i < nums.length; i++) { ors.add(new int[] { 0, i }); int j = 0; for (int[] or : ors) { or[0] |= nums[i]; if (or[0] >= k) { minLength = Math.min(minLength, i - or[1] + 1); } if (ors.get(j)[0] == or[0]) { ors.get(j)[1] = or[1]; } else { ors.set(++j, or); } } ors.subList(j + 1, ors.size()).clear(); } return minLength == Integer.MAX_VALUE ? -1 : minLength; } }
|
还有更多子数组相关题目,后续再遇到时继续总结…