在算法面试和刷题过程中,子数组和区间问题是高频考点。今天我们聚焦一道经典题目——长度最小的子数组(LeetCode 209),它不仅考察你对数组的基本操作,更是滑动窗口思想的绝佳练习。本文将带你深入理解题意、分析思路,并用最简洁的方式实现高效解法。
一、题目描述
给定一个含有 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
题目链接: leetcode 209.题目链接
二、题目分析
1. 题意拆解
- 连续子数组:必须是数组中一段连续的元素。
- 和 ≥ s:子数组所有元素之和要大于等于目标值 s。
- 最小长度:在所有满足条件的子数组中,返回长度最小的那一个。
2. 暴力解法的瓶颈
最直接的思路是:枚举所有可能的子数组,计算每个子数组的和,判断是否满足条件,记录最小长度。
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
对于 n 达到 10^5 的数据规模,暴力法会超时,必须优化。
三、滑动窗口思想
1. 什么是滑动窗口?
滑动窗口是一种高效处理区间问题的技巧。它通过维护一个“窗口”区间,不断调整窗口的左右边界,动态维护区间内的状态(如和、最大/最小值等),从而避免重复计算。
2. 本题滑动窗口的核心思路
- 用两个指针
left和right表示窗口的左右边界。 right向右扩展窗口,累加窗口内的和sum。- 每当
sum≥ target 时,尝试收缩左边界left,以获得更短的子数组,并更新最小长度。 - 不断重复,直到遍历完整个数组。
四、代码实现与详细注释
下面是标准的滑动窗口解法:
function minSubArrayLen(target, nums) {
let len = nums.length;
let minLen = Infinity; // 初始化为无穷大
let sum = 0; // 当前窗口的和
let left = 0; // 左边界
let right = 0; // 有边界
while(right < len) {
sum += nums[right]; // 扩展右边界
// 当窗口内的和大于等于 target 时,尝试收缩左边界
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1); // 更新最小长度
sum -= nums[left]; // 收缩左边界
left++;
}
right++
}
// 如果没有找到符合条件的子数组,返回0
return minLen === Infinity ? 0 : minLen;
}
代码详解
minLen用于记录当前找到的最小长度,初始为Infinity,这样便于后续比较。- 外层
while循环移动右边界 right ,逐步扩展窗口 - 内层
while循环在满足条件时,尽可能收缩左边界,寻找更短的子数组。 - 每次收缩窗口时,更新
minLen。 - 最后判断
minLen是否被更新过,若未更新说明不存在符合条件的子数组,返回0。
五、滑动窗口的执行过程举例
以示例 target = 7, nums = [2,3,1,2,4,3] 为例,滑动窗口的变化如下:
right=0,sum=2,不满足right=1,sum=5,不满足right=2,sum=6,不满足right=3,sum=8,满足,窗口长度4,尝试收缩- 收缩后 sum=6,不满足
right=4,sum=10,满足,窗口长度3,继续收缩- 收缩后 sum=7,窗口长度2,继续收缩
- 收缩后 sum=4,不满足
right=5,sum=7,满足,窗口长度2,继续收缩- 收缩后 sum=3,不满足
最终,最小长度为2。
六、常见问题与细节
1. 为什么 minLen 初始值用 Infinity?
如果用0,Math.min(0, x) 永远是0,无法正确更新。用 Infinity 可以确保第一次找到满足条件的子数组时,minLen 会被正确赋值。
2. 为什么要用 while 循环收缩左边界?
因为每次右边界扩展后,可能窗口内的和远大于 target,需要多次收缩左边界,才能找到最短的子数组。
3. 时间复杂度分析
- 每个元素最多被访问两次(一次右扩、一次左缩),总复杂度 O(n),非常高效。
七、总结
- 本题是滑动窗口思想的典型应用,适合初学者练习。
- 通过维护窗口的左右边界,动态调整区间,极大提升效率。
- 代码简洁,易于理解,适合面试和刷题时快速实现。
八、完整代码
function minSubArrayLen(target, nums) {
let len = nums.length;
let minLen = Infinity;
let sum = 0;
let left = 0;
let right = 0;
while(right < len) {
sum += nums[right];
while (sum >= target) {
minLen = Math.min(minLen, right - left + 1);
sum -= nums[left]; 界
left++;
}
right++
}
return minLen === Infinity ? 0 : minLen;
}
九、结语
滑动窗口是解决区间类问题的利器。通过本题的练习,你不仅能掌握滑动窗口的基本用法,还能体会到算法优化的乐趣。希望你能举一反三,将这一技巧应用到更多类似问题中!