目录
一、代码逐行解释(滑动窗口解法:最短子数组长度)
原代码完整拆解
原代码存在的 BUG & 缺陷
二、标准优化版滑动窗口(双指针)
优化思路
三、优化点对比说明
四、逻辑流程演示(举例)
五、补充:二分前缀和解法(进阶 O (nlogn))
一、代码逐行解释(滑动窗口解法:最短子数组长度)
题目:LeetCode 209 长度最小的子数组 给定正整数数组nums和目标值target,找出总和 ≥ target 的连续子数组的最小长度,不存在返回 0。
原代码完整拆解
class Solution { public int minSubArrayLen(int target, int[] nums) { // 记录最小窗口长度,初始为最大值(代表没找到合法窗口) int min = Integer.MAX_VALUE; // 当前窗口 [left, right] 的元素和 int sum = 0; int left = 0; // 窗口左边界 int right = 0;// 窗口右边界 int length = nums.length; sum = nums[0]; // 先把第一个元素加入窗口 // 第一个元素直接大于target,最短长度就是1,直接返回 if(sum>target){ return 1; } // 外层循环:右指针最多走到倒数第二个元素 while (right<length-1){ // 内层1:窗口和不足target,不断右移右指针扩大窗口 while (sum<target&&right<length-1){ right++; sum += nums[right]; } // 内层2:窗口和满足条件,不断右移左指针缩小窗口,更新最小长度 while (sum>=target){ if(min>right - left+1){ min = right - left +1; } sum -= nums[left]; left++; } } // 如果min没更新过,说明无合法子数组返回0,否则返回最小长度 return min == Integer.MAX_VALUE?0:min; } }原代码存在的 BUG & 缺陷
- 边界初始化缺陷
- 数组为空、数组长度 = 1 时会报错
nums[0]数组越界; - 提前写死
sum = nums[0],破坏滑动窗口统一初始化逻辑。
- 数组为空、数组长度 = 1 时会报错
- 外层循环限制错误
right < length -1导致右指针无法走到数组最后一位,最后一个元素永远无法纳入窗口,测试用例极易出错。 - 冗余特判
if(sum>target) return 1滑动窗口内部逻辑完全可以覆盖该场景,属于多余代码。 - 逻辑耦合、可读性差三层嵌套循环结构臃肿,标准滑动窗口只需要一层外层循环控制右指针。
- 变量命名尚可,但流程不标准
二、标准优化版滑动窗口(双指针)
优化思路
- 统一初始化:
sum=0,left=0,右指针从头遍历到尾,消除数组越界; - 去掉多余特判,一套逻辑覆盖所有场景;
- 单层外层循环控制右指针,满足和≥target 时收缩左边界;
- 增加数组判空,鲁棒性更强;
- 简化长度更新代码;
- 时间复杂度 O (n),每个元素仅被左右指针各访问一次。
class Solution { public int minSubArrayLen(int target, int[] nums) { // 边界:空数组直接返回0 if (nums == null || nums.length == 0) { return 0; } int n = nums.length; int minLen = Integer.MAX_VALUE; int sum = 0; int left = 0; // 右指针从头到尾遍历,一层循环即可 for (int right = 0; right < n; right++) { sum += nums[right]; // 扩大右边界 // 当窗口和满足条件,持续收缩左边界,更新最小长度 while (sum >= target) { minLen = Math.min(minLen, right - left + 1); sum -= nums[left]; left++; } } // 未找到合法子数组返回0 return minLen == Integer.MAX_VALUE ? 0 : minLen; } }三、优化点对比说明
| 问题 | 优化方案 |
|---|---|
| 访问 nums [0] 越界 | sum 初始 0,循环内逐个加 nums [right],兼容空 / 长度 1 数组 |
| right 走不到末尾 | for 循环 right < n,完整遍历全部元素 |
| 多余 if 特判单个元素 | while 收缩窗口自动处理单元素满足 target 场景 |
| 三层循环嵌套臃肿 | 外层单层 for,内层仅收缩窗口的 while,逻辑清晰 |
| 手动比较更新 min | 使用Math.min简化代码 |
| 无空数组防护 | 开头增加 nums 判空,线上不会 NPE / 数组越界 |
四、逻辑流程演示(举例)
target=7,nums=[2,3,1,2,4,3]
- right 不断右移累加 sum,直到 sum≥7
- right=3 sum=8,进入收缩循环:窗口 [0,3] 长度 4,min=4;sum-=2=6,left=1 退出收缩
- right=4 sum=10,收缩:
- [1,4] 长度 4 → min 不变;sum-3=7 left=2
- [2,4] 长度 3 → min=3;sum-1=6 left=3 退出
- right=5 sum=9,收缩:
- [3,5] 长度 3 → min 不变;sum-2=7 left=4
- [4,5] 长度 2 → min=2;sum-4=3 left=5 退出 最终返回 2,正确答案。
五、补充:二分前缀和解法(进阶 O (nlogn))
如果题目允许,还可以用前缀和 + 二分查找,适合数据量极大场景:
class Solution { public int minSubArrayLen(int target, int[] nums) { int n = nums.length; int[] preSum = new int[n + 1]; for (int i = 0; i < n; i++) { preSum[i + 1] = preSum[i] + nums[i]; } int min = Integer.MAX_VALUE; for (int i = 1; i <= n; i++) { int aim = preSum[i] - target; // 二分找最大j满足preSum[j] <= aim int l = 0, r = i; while (l < r) { int mid = l + r >> 1; if (preSum[mid] <= aim) l = mid + 1; else r = mid; } if (l > 0) { min = Math.min(min, i - l + 1); } } return min == Integer.MAX_VALUE ? 0 : min; } }日常刷题优先滑动窗口 O (n),效率更高、代码更简洁。