这道题比较好的一道题,通过分析来对数据结构进行选型,是思考比较难,但如果路子清晰就很明朗的一道题。说下思路,首先根据题意看要找一段子数组满足和至少为k,这种情况不难想到肯定要用前缀和来做求每一段和值好进行比较,但如何去维护一个最短的长度是难点,一点点分析,我们构建好前缀和数组后,从头开始遍历,假设从某一处i
开始用preSum[i]-preSum[j] >= k
计算到j
的长度已经满足条件,那么i
继续向后的就没必要对j
进行计算了,所以当第一次遍历到i
就是以j
开头的最短满足条件的长度,这时候就可以将j
的前缀和删掉,还有另外一种情况,假设从j < i
,如果当前的preSum[i]
要是小于preSum[j]
的话,那么后面的某一个k
处,preSum[k]
减去preSum[i]
带来的收益更大,且长度最小,所以需要一种数据结构是从首和尾都删除元素的,且排序添加,所以使用单调双端队列。
func shortestSubarray(nums []int, k int) int {
n := len(nums)
// 前缀和
preSum := make([]int, n+1)
for i, v := range nums {
preSum[i+1] = preSum[i] + v
}
res := n + 1
q := []int{}
for i, v := range preSum {
// 如果队首的前缀和到当前i位置这一段满足大于k,则计算长度,后续遇见再大的前缀和,也满足条件,都不是最小的,因为其长度一定会大于i-q[0]
for len(q) > 0 && v - preSum[q[0]] >= k {
res = min(res, i-q[0])
// 出队列
q = q[1:]
}
// 从队尾,即最大端开始将前缀和大于v的都剔除,因为从这些任何一个大于v的位置开始,都不如从v开始带来的收益多,而且长度也会更大。
for len(q) > 0 && preSum[q[len(q)-1]] >= v {
q = q[:len(q)-1]
}
q = append(q, i)
}
if res < n+1 {
return res
}
return -1
}
func min(a, b int) int {
if a < b {
return a
}
return b
}