启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
该题是数组长度最小的子数组题型第一题。
题目来源
34. 在排序数组中查找元素的第一个和最后一个位置(LeetCode)
题目描述(中等)
给定一个含有 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
提示
进阶
- 如果你已经实现
O(n)时间复杂度的解法, 请尝试设计一个O(n log(n))时间复杂度的解法。
题目解析
数组 nums 中的每个下标都可以作为子数组的开始下标,对于每个开始下标 i ,需要找到大于或等于 i 的最小下标 j ,使得 [i, j] 区间的所有元素和大于或等于 target ,并更新子数组的最小长度 j - i + 1 。
滑动窗口(双指针)
该方法定义慢指针 slow 和快指针 fast 两个指针,维护变量 sum 作为子数组的元素和( nums[slow] 到 nums[fast] 的元素和 )。
明确快慢指针的用处:
- 慢指针(
slow):子数组的 开始位置 - 快指针(
fast):子数组的 结束位置 和 遍历数组
初始状态下, slow 和 fast 都指向下标 0 , sum 的值为 0 ,子数组最小长度 res 值为 Number.MAX_VALUE (便于判断数组是否有满足条件的最小长度)。
每次迭代中,将 nums[fast] 加到 sum ,如果 sum >= target ,则更新子数组的最小长度(判断此时 fast - slow + 1 是否比 res 小,如果 fast - slow + 1 小于 res ,更新 res 值为 fast -slow + 1 ,反之,不更新),然后将 nums[slow] 从 sum 中减去,随后 slow 右移(加1),直到 sum < target 。每次迭代结束时,fast 右移(加1)。
代码
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let slow = 0, fast = 0, length = nums.length, res = Number.MAX_VALUE, sum = 0
while (fast <= length) {
if (sum >= target) {
res = Math.min(res, fast - slow)
sum -= nums[slow]
slow++
}else {
if (fast === length) {
break
}
sum += nums[fast]
fast++
}
}
return res === Number.MAX_VALUE ? 0 : res
};
- 时间复杂度:
- 空间复杂度:
如图:
代码(优化)
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let slow = 0, fast = 0, length = nums.length, res = Number.MAX_VALUE, sum = 0
while (fast <= length) {
sum += nums[fast]
while (sum >= target) {
res = Math.min(res, fast - slow + 1)
sum -= nums[slow]
slow++
}
fast++
}
return res === Number.MAX_VALUE ? 0 : res
};
如图:
前缀 + 二分查找
该方法为了使用二分查找,需要额外创建一个数组 sum 用于存储数组 nums 的前缀和,其中 sum[i] 表示从 nums[0] 到 nums[i] 的元素和。得到前缀和之后,对于每个开始下标 i ,可通过二分查找得到大于或等于 i 的最小下标 bound ,使得 sum[bound] - sum[i] >= target ,并更新子数组的最小长度。
sum[0] 代表 0 个元素和。
因为该题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分查找的正确性。如果题目中没有说明数组中每个元素都为正,这里就不能使用二分查找找到这个位置了。
代码
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let sum = [0], length = nums.length, res = Number.MAX_VALUE
for (let i = 1;i <= length;i++) {
sum.push(sum[i - 1] + nums[i - 1])
}
for (let i = 0;i <= length;i++) {
let bound = search(sum, 0, length, target + sum[i])
if (bound != -1){
res = Math.min(res, bound - i + 1)
}
}
return res === Number.MAX_VALUE ? 0 : res
};
var search = function(sum, left, right, target) {
if (sum[right] < target) {
return -1
}
while (right >= left) {
let mid = Math.floor((left + right) / 2)
if (sum[mid] >= target) {
right = mid - 1
}else {
left = mid + 1
}
}
return left - 1
}
- 时间复杂度:
- 空间复杂度:
如图:
执行用时和内存消耗仅供参考,大家可以多提交几次。如有更好的想法,欢迎大家提出。