0209.长度最小的子数组
难度:🟡 中等
标签:数组
、二分查找
、前缀和
、滑动窗口
题目描述
给定一个含有 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
的、长度最短的连续子数组。由于所有元素都是正数,子数组的和会随着其长度的增加而单调递增,这为我们使用滑动窗口提供了完美的基础。
思路选择
滑动窗口法 是解决此问题的标准最优思路。
我们可以使用两个指针,
left
(窗口左边界)和right
(窗口右边界),来定义一个“窗口”。right
指针负责向右移动,不断地将新元素纳入窗口,并累加到窗口的和sum
中。一旦窗口内的和
sum
达到了target
的要求,我们就找到了一个满足条件的子数组。此时,我们记录下它的长度,并尝试收缩窗口——即让left
指针向右移动,并从sum
中减去移出的元素。我们持续收缩窗口,直到
sum
不再满足>= target
的条件,然后再继续移动right
指针扩大窗口,如此往复,直到right
遍历完整个数组。
关键步骤
初始化:
left = 0
,right = 0
。窗口内元素和
sum = 0
。最小长度
minLength = Infinity
。
扩展窗口:外层循环控制
right
指针从头到尾遍历数组。 a. 将nums[right]
加入sum
。收缩窗口:内层
while
循环检查sum
是否大于等于target
。 a. 如果是,则更新minLength = Math.min(minLength, right - left + 1)
。 b. 从sum
中减去nums[left]
,并将left
指针右移一位,完成窗口的收缩。返回结果:外层循环结束后,如果
minLength
仍然是Infinity
,说明没有找到任何满足条件的子数组,返回0
。否则,返回minLength
。
代码实现
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function (target, nums) {
let left = 0, right = 0, minLen = Infinity, sum = 0;
// right 指针负责扩展窗口
while (right < nums.length) {
sum += nums[right];
right++; // 注意:这里 right 已经指向了窗口外的下一个元素
// sum 满足条件后,收缩窗口
while (sum >= target) {
// 此时窗口长度是 right - left
minLen = Math.min(minLen, right - left);
sum -= nums[left];
left++;
}
}
return minLen === Infinity ? 0 : minLen;
};
复杂度分析
时间复杂度:O(n) 其中 n 是数组
nums
的长度。left
和right
两个指针都最多只会遍历整个数组一次。空间复杂度:O(1) 我们只使用了常数个额外变量,空间消耗是恒定的。
相关题目
总结
本题是滑动窗口算法的经典应用之一。该算法的核心在于通过两个指针维护一个可变窗口,并根据窗口内的状态来决定是扩展还是收缩窗口,从而在线性时间内找到满足特定条件的最佳子数组或子串。