LeetCode 862. 和至少为 K 的最短子数组
返回
A的最短的非空连续子数组的长度,该子数组的和至少为K。
如果没有和至少为K的非空子数组,返回-1。
思路
初看这道题类似最大子数组和,本来打算用滑动窗口解决,但是超时。
看完题解可以使用 前缀和数组 和 递增队列 解。
官方的题解不是很清楚,这里试着详细解释一下。
前缀和数组
P[i]表示数组前i个元素的和。那么本题就可以转化为
y>xP[y]−P[x]>=K(1)
y > x \\
P[y] - P[x] >= K
\tag{1}
y>xP[y]−P[x]>=K(1)
obj[y]=xobj[y] = xobj[y]=x表示对于yyy来说 最大的满足 公式(1)的 xxx。
这里有一个推论:
如果有x1<x2x1<x2x1<x2,但是P[x1]>P[x2]P[x1]>P[x2]P[x1]>P[x2]。那么对于固定的yyy来说,obj[y]=x2obj[y] = x2obj[y]=x2。因为此时如果P[y]−P[x1]>=KP[y] - P[x1] >= KP[y]−P[x1]>=K那么P[y]−P[x2]P[y] - P[x2]P[y]−P[x2]肯定大于KKK,且y−x2<y−x1y-x2 < y-x1y−x2<y−x1。
所以对于固定的yyy来说,找obj[y]=xobj[y] = xobj[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
}



被折叠的 条评论
为什么被折叠?



