长度最小的子数组
数组 滑动窗口 双指针题目描述
给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。
如果不存在符合条件的子数组,返回 0。
示例:
思路分析
这道题的核心是找"连续子数组",暴力解法是枚举所有子数组并计算和,时间复杂度 O(n²) 或 O(n³)。而滑动窗口(双指针)能将复杂度降低到 O(n)。
滑动窗口的工作原理
滑动窗口是双指针的一种应用形式,维护一个可变长度的"窗口"在数组上滑动:
- 右指针(right):不断向右扩展窗口,将新元素加入窗口。
- 左指针(left):当窗口内的和满足条件时,尝试收缩窗口左边界,寻找更小的满足条件的子数组。
这种"扩展-收缩"的策略能够高效地遍历所有可能的子数组,而不需要重复计算。
为什么滑动窗口有效?
关键在于单调性:当窗口和 sum ≥ target 时,继续扩展右边界只会让和更大,此时应该收缩左边界;而当 sum < target 时,必须扩展右边界才能让和增大。
这种单调关系保证了:
- 每个元素最多被左指针和右指针各访问一次。
- 不会遗漏任何可能的最小子数组。
算法步骤
- 初始化左指针
left = 0,右指针right = 0,窗口和sum = 0,最小长度minLen = Infinity。 - 右指针从 0 遍历到
n-1,每次循环:- 将
nums[right]加入窗口,更新sum。 - 当
sum ≥ target时,进入内层循环:- 记录当前窗口长度
right - left + 1,更新minLen。 - 将
nums[left]从窗口移除,更新sum。 - 左指针右移
left++,继续尝试收缩窗口。
- 记录当前窗口长度
- 将
- 循环结束后,如果
minLen仍是Infinity,说明没有满足条件的子数组,返回0;否则返回minLen。
边界情况处理
- 空数组:直接返回
0。 - target 非正数:题目要求为正整数,非正数情况下返回
0。 - 无解情况:所有元素之和都小于
target,minLen保持Infinity,最终返回0。
示例代码
复杂度分析
- 时间复杂度:
O(n),其中n是数组长度。虽然有两层循环,但左右指针各最多移动n次,每个元素最多被访问两次。 - 空间复杂度:
O(1),只使用了固定数量的变量(left、right、sum、minLen)。
示例演示
以 target = 7, nums = [2,3,1,2,4,3] 为例:
窗口滑动过程
详细解释:
- 轮次 1-3:右指针不断扩展,但 sum < 7,无法收缩。
- 轮次 4:
sum = 8 ≥ 7,记录长度 4,移除nums[0]=2,左指针移到 1。 - 轮次 5:加入 4,
sum = 10,持续收缩:- 移除
nums[1]=3,sum=7,记录长度 4。 - 移除
nums[2]=1,sum=6 < 7,停止收缩。 - 移除
nums[3]=2,sum=4 < 7,停止收缩。
- 移除
- 轮次 6:加入 3,
sum = 7,持续收缩:- 当前窗口
[2,4,3],长度 3,更新 minLen = 3。 - 移除
nums[3]=2,sum=7,窗口[4,3],长度 2,更新 minLen = 2。 - 移除
nums[4]=4,sum=3 < 7,停止收缩。
- 当前窗口
最终返回 minLen = 2,对应子数组 [4,3]。
关键点总结
- 扩展阶段:右指针每次移动都会将新元素加入窗口。
- 收缩阶段:当满足条件时,左指针持续移动直到不再满足条件,这个过程中不断更新最小长度。
- 时间复杂度保证:左右指针都是单向移动,不会回退,因此总体时间复杂度为
O(n)。