LeetCode 862. 和至少为 K 的最短子数组
返回
A
的最短的非空连续子数组的长度,该子数组的和至少为K
。
如果没有和至少为K
的非空子数组,返回-1
。
思路
初看这道题类似最大子数组和,本来打算用滑动窗口解决,但是超时。
看完题解可以使用 前缀和数组 和 递增队列 解。
官方的题解不是很清楚,这里试着详细解释一下。
前缀和数组
P[i]
表示数组前i
个元素的和。那么本题就可以转化为
y
>
x
P
[
y
]
−
P
[
x
]
>
=
K
(1)
y > x \\ P[y] - P[x] >= K \tag{1}
y>xP[y]−P[x]>=K(1)
o
b
j
[
y
]
=
x
obj[y] = x
obj[y]=x表示对于
y
y
y来说 最大的满足 公式(1)的
x
x
x。
这里有一个推论:
如果有 x 1 < x 2 x1<x2 x1<x2,但是 P [ x 1 ] > P [ x 2 ] P[x1]>P[x2] P[x1]>P[x2]。那么对于固定的 y y y来说, o b j [ y ] = x 2 obj[y] = x2 obj[y]=x2。因为此时如果 P [ y ] − P [ x 1 ] > = K P[y] - P[x1] >= K P[y]−P[x1]>=K那么 P [ y ] − P [ x 2 ] P[y] - P[x2] P[y]−P[x2]肯定大于 K K K,且 y − x 2 < y − x 1 y-x2 < y-x1 y−x2<y−x1。
所以对于固定的 y y y来说,找 o b j [ y ] = x obj[y] = x obj[y]=x的过程只需要考虑那些满足 P [ x ] P[x] P[x]递增的x。所以可以构造一个递增队列来求解。
下面更具代码来解释,因为go构造队列太麻烦了,本文使用双指针数组实现双端队列。
代码
func shortestSubarray(A []int, K int) int {
result := len(A) + 1
P := make([]int, len(A)+1, len(A)+1) //前缀和数组,P[i]表示数组前i个元素的和。
IncreaQ := make([]int, len(A)+1, len(A)+1) //递增队列
P[0] = 0 //在最前面加一项0,前0个元素的和是0
IncreaQ[0] = 0
for i:=0;i<len(A);i++ { //构造前缀和数组
P[i+1] = P[i] + A[i]
}
leftL := 0 //双指针实现双端队列
rightL := 0
for i:=1;i<=len(A);i++ { // 遍历P数组
for rightL >= leftL { //删除队尾
// 从递增队列IncreaQ中删除队尾的大于P[i]的项。因为,如果过那些项满足条件的话,P[i]更满足了。此时i是更优解。
if(P[i] < P[IncreaQ[rightL]]) {
rightL--
} else {
break
}
}
rightL++
IncreaQ[rightL] = i
for leftL <=rightL {
// 从前开始删除所有满足 P[i] - P[x] >= K 的元素。
// 因为,对于i之后的P[]来说,如果这些元素也满足公式(1)的话。
// 他们的结果肯定比i大,所以对于i以后的解来说,不用考虑这些被删掉的。
if( P[i] - P[IncreaQ[leftL]] >= K ) {
if( i - IncreaQ[leftL] < result ) {
result = i - IncreaQ[leftL]
}
leftL++
} else {
break
}
}
}
// 边构造边遍历递增队列,构造完就遍历完了。
if result == len(A) + 1 {
return -1
}
return result
}