给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
- 输入:s = 7, nums = [2,3,1,2,4,3]
- 输出:2
- 解释:子数组 [4,3] 是该条件下的长度最小的子数组。
提示:
- 1 <= target <= 10^9
- 1 <= nums.length <= 10^5
- 1 <= nums[i] <= 10^5
思路
暴力解法(运行时间超时)
使用双层for循环,获取所有可能的子数组,取符合条件的子数组中的最小长度
function minSubArrayLen(target: number, nums: number[]): number {
let len: number = nums.length
// 初始化子数组长度len+1,查找到符合条件的任意子数组的长度都会比初始值小
let n: number = len + 1
let sum: number = 0
// 双层for循环,遍历获取所有可能的子数组组合
for (let i = 0; i < len; i++) {
// 每次修改子数组起始位置的遍历时,都将子数组初始化和置0
sum = 0
// 修改子数组结束位置,子数组长度依次递增
for (let j = i; j < len; j++) {
// 更新子数组的和
sum += nums[j]
// 比较子数组和与目标值
if (sum >= target) {
// 判断当前符合条件的子数组的长度是否是最小的
n = j - i + 1 <= n ? j - i + 1 : n
break
}
}
}
// 若长度值还为初始值,表示暂无满足条件的子数组,返回0
return n > len ? 0 : n
};
滑动窗口
滑动窗口就是不断的调整子序列的开始位置和结束位置,从而得到我们想要的结果。
暴力解法通过两个for循环嵌套去改变子数组的初始和结束位置,而滑动窗口通过for循环改变子数组的结束位置,当子数组元素之和符合条件时,获取其长度,此时移动子数组的初始位置,直至子数组长度小于目标值,则继续移动子数组结束位置,从而获取到满足条件的子数组的最小长度。
function minSubArrayLen(target: number, nums: number[]): number {
// 起始指针
let left: number = 0,
// 返回值初始化为最大值
res: number = Infinity,
// 符合条件的子数组长度
subLen: number = 0,
// 子数组的和
sum: number = 0
// 遍历终止位置指针
for (let right: number = 0; right < nums.length; right++) {
sum += nums[right]
// 当子数组符合条件时
while (sum >= target) {
// 获取符合条件的最小子数组长度
subLen = right - left + 1
res = Math.min(res, subLen)
// 子数组初始指针右移,子数组长度减一,子数组和更新,若子数组和不符合条件,则子数组终止位置指针右移
sum -= nums[left]
left++
}
}
// 若返回值还为最大值,表示没有符合条件的子数组,返回0
return res === Infinity ? 0 : res
}
滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。
- 时间复杂度:O(n),其中 n 是数组的长度。指针 start 和 end 最多各移动 n 次。
- 空间复杂度:O(1)。
for里放一个while的时间复杂度不一定是O(n^2), 主要是看每一个元素被操作的次数,每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)