【新手必看】滑动窗口秒杀“长度最小的子数组”——LeetCode 209题全解析!

95 阅读4分钟

一、开篇:你是否也被“子数组”题目难住了?

刷LeetCode时,遇到“长度最小的子数组”这类题目,你是不是也有过这样的疑惑:

  • “到底怎么才能又快又准地找到最短的子数组?”
  • “暴力法超时怎么办?”
  • “滑动窗口到底怎么用?”

别急,今天我就用最通俗的语言,带你彻底搞懂这道题!


二、题目简介

LeetCode 209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target,找出该数组中满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。

三、暴力法:新手的第一步

最直接的想法是什么?当然是“把所有可能的子数组都试一遍”!

代码实现

function minSubArrayLen(target, nums) {
    let minLen = Infinity;
    for (let i = 0; i < nums.length; i++) {
        let sum = 0;
        for (let j = i; j < nums.length; j++) {
            sum += nums[j];
            if (sum >= target) {
                minLen = Math.min(minLen, j - i + 1);
                break; // 已经满足条件,没必要再往后加了
            }
        }
    }
    return minLen === Infinity ? 0 : minLen;
}

复杂度分析

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)

缺点:当数组很大时,暴力法会超时!


四、滑动窗口:高效的“窗口”思维

滑动窗口是什么?

滑动窗口就像一扇可以左右移动的窗户,我们用两个指针(left和right)来标记窗口的左右边界。窗口内的元素之和如果小于target,就扩大窗口(右移right);如果大于等于target,就缩小窗口(右移left),同时记录最小长度。

代码实现

function minSubArrayLen(target, nums) {
    let left = 0, sum = 0, minLen = Infinity;
    for (let right = 0; right < nums.length; right++) {
        sum += nums[right];
        while (sum >= target) {
            minLen = Math.min(minLen, right - left + 1);
            sum -= nums[left];
            left++;
        }
    }
    return minLen === Infinity ? 0 : minLen;
}

过程详解

  1. 初始化:left=0,sum=0,minLen=Infinity
  2. 右指针扩展窗口:每次加上nums[right]
  3. 窗口内和满足条件:sum >= target
    • 记录当前窗口长度
    • 尝试缩小窗口(left右移),并更新sum
  4. 循环结束:返回minLen

动图演示

假设 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,尝试缩小)
  • left=1, sum=6(不满足,继续右移right)
  • right=4, sum=10(满足条件,窗口长度4,继续缩小)
  • left=2, sum=9(满足条件,窗口长度3,继续缩小)
  • left=3, sum=8(满足条件,窗口长度2,继续缩小)
  • left=4, sum=4(不满足,继续右移right)
  • right=5, sum=7(满足条件,窗口长度2)

最终最小长度为2。


五、代码优化与细节

  • 为什么用while循环?
    因为每次sum>=target时,我们都要尽可能缩小窗口,找到最短的长度。
  • 为什么要判断minLen是否为Infinity?
    如果没有任何子数组满足条件,应该返回0。

六、进阶思考

  • 如果数组中有负数怎么办?
    滑动窗口法只适用于全正数数组。如果有负数,问题会变复杂,需要用其他算法。
  • 还能不能再优化?
    目前的滑动窗口法已经是O(n)最优解了。

七、总结

  • 暴力法适合理解题意,但效率低
  • 滑动窗口法高效且易于实现,是解决“连续子数组”问题的利器
  • 多画图、多调试,滑动窗口思想会越来越熟练!

八、附录:完整代码

// 暴力法
function minSubArrayLen1(target, nums) {
    let minLen = Infinity;
    for (let i = 0; i < nums.length; i++) {
        let sum = 0;
        for (let j = i; j < nums.length; j++) {
            sum += nums[j];
            if (sum >= target) {
                minLen = Math.min(minLen, j - i + 1);
                break;
            }
        }
    }
    return minLen === Infinity ? 0 : minLen;
}

// 滑动窗口法
function minSubArrayLen(target, nums) {
    let left = 0, sum = 0, minLen = Infinity;
    for (let right = 0; right < nums.length; right++) {
        sum += nums[right];
        while (sum >= target) {
            minLen = Math.min(minLen, right - left + 1);
            sum -= nums[left];
            left++;
        }
    }
    return minLen === Infinity ? 0 : minLen;
}

九、写在最后

作为初学者,刚开始接触滑动窗口时可能会觉得抽象,但只要多练习几道类似的题目,你一定会发现它的强大。希望这篇文章能帮你扫清“长度最小的子数组”这道题的所有疑惑!