🚀 LeetCode 209:长度最小的子数组 —— 滑动窗口 + 双指针,一次吃透区间优化思想

7 阅读3分钟

这道题不是在考暴力,也不是在考前缀和,
而是在考你会不会“动态维护一个合法区间”。


一、题目回顾

给定一个含有 n正整数 的数组 nums,和一个正整数 target

请找出该数组中 满足其和 ≥ target 的长度最小的连续子数组,并返回其长度。
如果不存在符合条件的子数组,返回 0


二、暴力解法为什么不行?

最直接的思路是:

  • 枚举所有子数组
  • 计算它们的和
  • 找满足条件的最短长度

但这样做:

  • 子数组数量:O(n²)
  • 计算和:O(n)

👉 总复杂度 O(n³) ,直接超时。

即使优化成前缀和,也有:

  • 枚举区间:O(n²)

👉 还是不够好。


三、问题的关键信息(决定解法)

请注意题目中的一个 极其重要的隐藏条件

数组中的元素全部是正整数

这意味着:

  • 右指针右移 → 区间和只会变大
  • 左指针右移 → 区间和只会变小

👉 区间具有“单调性”
👉 非常适合:滑动窗口 + 双指针


四、核心思想:动态维护一个“合法窗口”

一句话理解

用一个窗口包住一段连续区间,
右边扩张让它合法,左边收缩让它最短。


五、滑动窗口整体思路

我们维护四个变量:

  • left:窗口左边界
  • right:窗口右边界
  • sumWindow:当前窗口内元素之和
  • minLen:满足条件的最小长度

算法流程

  1. right 不断向右移动,扩大窗口

  2. 累加窗口和 sumWindow

  3. sumWindow >= target

    • 说明当前窗口合法
    • 尝试不断移动 left,压缩窗口
  4. 在压缩过程中更新最小长度


六、代码实现(滑动窗口标准模板)

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int n = nums.length;
        int left = 0;
        int minLen = n + 1;
        int sumWindow = 0;

        for (int right = 0; right < n; right++) {
            sumWindow += nums[right];

            while (sumWindow >= target) {
                minLen = Math.min(minLen, right - left + 1);
                sumWindow -= nums[left];
                left++;
            }
        }

        return minLen == n + 1 ? 0 : minLen;
    }
}

七、为什么这里一定要用 while,而不是 if

这是滑动窗口的灵魂问题

❌ 如果用 if

if (sumWindow >= target) {
    // 只收缩一次
}

问题在于:

  • 当前窗口可能 还能继续收缩
  • if 只会错过更短的合法区间

✅ 用 while 的含义是:

只要窗口仍然合法,就一直缩,
直到它刚好不合法为止。

这样才能保证:

  • 当前 right 固定时
  • 找到的就是 最短合法区间

八、为什么这是“双指针”?

  • left:只增不减
  • right:只增不减
  • 每个指针最多走 n

👉 整体时间复杂度 O(n)

这正是“双指针优化暴力枚举”的经典范式。


九、时间 & 空间复杂度分析

⏱️ 时间复杂度

  • leftright 各遍历数组一次
    👉 O(n)

🧠 空间复杂度

  • 只用了常数变量
    👉 O(1)

十、如何一眼看出这是滑动窗口题?

看到这些关键词,请直接条件反射:

  • 连续子数组
  • 最小 / 最大长度
  • 满足某个条件(和 ≥ target)
  • 数组元素为正数

👉 90% 是滑动窗口 + 双指针


十一、通用滑动窗口模板

for (right ...) {
    扩张窗口

    while (窗口满足条件) {
        更新答案
        收缩窗口
    }
}

十二、一句话总结

滑动窗口的本质,不是“固定区间”,
而是“在合法条件下,把区间压缩到极致”。