题目链接:
862. 和至少为 K 的最短子数组 - 力扣(LeetCode)
题目:
给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。
子数组 是数组中 连续 的一部分。
示例 :
输入:nums = [-28, 81, -20, 28, -29], k = 89
输出:3
提示:
1 <= nums.length <= 105
-105 <= nums[i] <= 105
1 <= k <= 109
代码:
// i-1| i,| |
// A{2,|-1,|2,|5}
// sum{0,| 2,|1,|3,8}
// 当sum[i]-sum[j]>=key时,下标(i-j)中间的这一段就是要求的子数组之一,之后比较一下每次得出的子数组之长,拿到最短的返回
public int shortestSubarray(int[] A, int k) {
int minLength = Integer.MAX_VALUE;//最短子数组长度
long[] sum = new long[A.length + 1];//用一个数组来记录前n个数之和,从1开始维护方便运算sum[i] = sum[i-1]+A[i-1]
for (int i = 1; i <= A.length; i++) {//先获取前缀和,因为从1开始的,sum总共就是A.length+1个和
sum[i] = A[i - 1] + sum[i - 1];
}
//错了。。。有负数的情况下,相加后反而会变小;其实是因为少判断了一部分子数组
//比如这个测试点 A=[-28,81,-20,28,-29],k=89,其实把第一个数去掉就符合要求
/*for (int i = 1; i <= A.length; i++) {
if (sum[i] < k) continue;
for (int j = 0; j < i; j++) {//如果sum[i]大于k,说明满足要求了,然后找长度最短的
if (sum[i] - sum[j] >= k) {
minLength = minLength < i - j ? minLength : i - j;
System.out.println(minLength + "\t" + i + "\t" + j);
}
}
}*/
//使用单调栈;检查一下A[i-1]是不是负数,如果是负数,就不要他了
//比较sum[i]和sum[i-1],如果变小了,A[i-1]是个负数
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < sum.length; i++) {
if (i != 0) {
while (deque.size() > 0 && sum[deque.peekLast()] >= sum[i]) {
//如果sum[i]比sum[i-1]还小,说明A[i-1]是个负数,他不但影响前边的子数组废掉,还会影响后边的数字相加后变小,他得滚蛋,同时把前边的子数组拖下水
deque.pollLast();
}
//第一次deque.peekFirst是0,在0被出队之前,全都相当于sum[i]>=k
while (deque.size() > 0 && sum[i] - sum[deque.peekFirst()] >= k) {
//如果子数组满足要求了,尝试去掉最前边的数字来找到最短的子数组
minLength = Math.min(minLength, i - deque.peekFirst());
deque.pollFirst();
}
}
deque.offerLast(i);
}
return minLength == Integer.MAX_VALUE ? -1 : minLength;//如果minLength还是Max_Value,说明不存在满足要求的,返回-1
}
思路都在注释了,看的一些大佬的思路,一开始自己写,没想到用单调栈进行优化。关键是要想到用前缀和,sum[ i ] = sum[i - 1]+A[i - 1]