题干
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其总和大于等于target的长度最小的
子数组
[numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度 。 如果不存在符合条件的子数组,返回 0 。
示例 1:
输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入: target = 4, nums = [1,4,4]
输出: 1
示例 3:
输入: target = 11, nums = [1,1,1,1,1,1,1,1]
输出: 0
题解
最简单的是暴力法,遍历每一个可能的子数组起点,对每一个起点从左到右寻找第一个满足和大于等于target的终点下标,并维护一个minLen记录最小的满足条件的子数组长度。算法时间复杂度为O(n^2),空间复杂度为O(1)。
var maxLen int = int(math.Pow(10, 5)) + 1
func minSubArrayLen(target int, nums []int) int {
n := len(nums)
minLen := maxLen
for i := 0; i < n; i++ {
sum := 0
for j := i; j < n; j++ {
sum += nums[j]
if sum >= target {
minLen = min(j-i+1, minLen)
break
}
}
}
if minLen == maxLen {
return 0
}
return minLen
}
相较于暴力,更好一点的做法是在寻找满足条件的终点时,使用二分查找。二分查找要求数组有序,注意到数组中的每个元素都是非负的,所以数组的前缀和数组一定是递增的,满足二分查找的条件。设前缀和数组为sums,sums[i]是nums[0] ~ nums[i-1]的和,sums[0] = 0,对于任意一个起点i,可以使用二分查找寻找一个最小的终点j满足sums[j]-sums[i] >= target。算法的时间复杂度为O(nlogn),空间复杂度为O(n)。
var maxLen int = int(math.Pow(10, 5)) + 1
func minSubArrayLen(target int, nums []int) int {
n := len(nums)
minLen := maxLen
// 计算前缀和数组,sums[0] = 0
sums := make([]int, n+1)
for i := 1; i <= n; i++ {
sums[i] = sums[i-1] + nums[i-1]
}
// 遍历所有可能的子数组起点,进行二分搜索使得和>=target的最小终点
for i := 0; i < n; i++ {
left, right := i+1, n
for left < right {
mid := (left + right) >> 1
if sums[mid]-sums[i] >= target {
right = mid
} else {
left = mid + 1
}
}
// 注意这里可能二分查找没有找到符合条件的终点,不能计入结果
if sums[right]-sums[i] >= target {
minLen = min(minLen, right-i)
}
}
if minLen == maxLen {
return 0
}
最好的做法是滑动窗口,维护一个滑动窗口,滑动窗口的左右指针即为子数组的范围,对于每一个滑动窗口位置,我们计算子数组元素的和sum,当sum >= target时,说明当前滑动窗口是满足条件的,但是它不一定是最小的,所以我们将滑动窗口的左指针右移;当sum < target的时候,说明当前滑动窗口不满足条件,需要加上更多的数,因此我们将滑动窗口的右指针右移。算法的时间复杂度为O(n),空间复杂度为O(1)。
var maxLen int = int(math.Pow(10, 5)) + 1
// 滑动窗口
func minSubArrayLen(target int, nums []int) int {
sum := 0
// 维护滑动窗口的左右端点,初始值为0和-1
left, right := 0, -1
n := len(nums)
minLen := maxLen
for right < n {
// 当前和>=target的时候,计算当前子数组长度,并将滑动窗口的左指针右移
// 当前和<target的时候,将滑动窗口右指针右移
if sum >= target {
minLen = min(right-left+1, minLen)
sum -= nums[left]
left++
if left > right {
right = left
}
} else {
right++
if right == n {
break
}
sum += nums[right]
}
}
if minLen == maxLen {
return 0
}
return minLen
}