题目描述
给定一个含有 n 个正整数的数组和一个正整数 target ,找出该数组中满足其和 ≥ target 的长度最小的 连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例
- 输入:target = 7, nums = [2,3,1,2,4,3]
- 输出:2(解释:子数组 [4,3] 的和为 7,长度最小)
- 输入:target = 11, nums = [1,1,1,1,1,1,1,1]
- 输出:0(无满足条件的子数组)
二、解题思路:滑动窗口(双指针)法
你的代码采用了 滑动窗口(双指针) 这一高效解法,核心思想是:用一个 "窗口"(左指针 j 、右指针 i 划定的连续子数组)动态调整范围,始终维护窗口内元素和 sum ≥ target,并记录最小窗口长度。
为什么选择滑动窗口?
- 暴力解法(枚举所有子数组)时间复杂度为 O(n²),对于大规模数组效率过低;
- 滑动窗口通过 "右指针扩张窗口、左指针收缩窗口" 的方式,仅遍历数组一次,时间复杂度优化至 O(n),空间复杂度为 O(1)(无需额外辅助空间)。
三、代码逐行解析
cpp
int minSubArrayLen(int target, vector<int>& nums) {
int j = 0; // 左指针:窗口起始位置
int result = nums.size(); // 初始化最小长度为数组总长度(最坏情况)
int sum = 0; // 窗口内元素的和
int sub = 0; // 当前窗口的长度
int a = 0; // 数组所有元素的总和(用于判断是否无解)
// 右指针i扩张窗口,遍历数组每个元素
for (int i = 0; i < nums.size(); i++) {
sum += nums[i]; // 右指针加入当前元素,扩大窗口和
a += nums[i]; // 累加计算数组总长度
// 当窗口和≥target时,尝试收缩左指针,寻找最小窗口
while (sum >= target) {
sub = i - j + 1; // 计算当前窗口长度(i-j+1:闭区间长度)
result = min(result, sub); // 更新最小窗口长度
sum -= nums[j]; // 左指针移出当前元素,缩小窗口和
j++; // 左指针右移,继续尝试收缩
}
}
// 若数组总和<target,说明无解,返回0
if (a < target) {
return 0;
}
return result; // 返回最小窗口长度
}
关键步骤拆解
- 初始化变量:
-
- 左指针 j 从 0 开始,确保窗口从数组起始位置出发;
-
- result 初始化为数组长度,因为最小窗口长度不可能超过数组本身;
-
- sum 记录当前窗口内元素和,a 记录数组总元素和(用于后续判断是否存在解)。
- 右指针扩张窗口:
-
- 循环中 i 作为右指针,依次将数组元素加入窗口(sum += nums[i]),持续扩大窗口范围,直到窗口和 sum ≥ target。
- 左指针收缩窗口(核心逻辑):
-
- 当 sum ≥ target 时,进入 while 循环:
-
-
- 计算当前窗口长度 sub = i - j + 1(注意闭区间长度公式);
-
-
-
- 用 min(result, sub) 更新最小窗口长度,确保 result 始终存储当前最小値;
-
-
-
- 移除左指针指向的元素(sum -= nums[j]),并右移左指针 j,收缩窗口,尝试寻找更短的有效窗口。
-
-
- 这里用 while 而非 if,是因为收缩一次后,窗口和可能仍≥target,需要持续收缩(例如数组 [2,2,3],target=5:初始窗口 [2,2,3] 和为 7,收缩左指针后 [2,3] 和为 5,仍满足条件,需继续更新最小长度)。
- 无解判断:
-
- 循环结束后,若 a < target(数组所有元素和都小于目标值),说明不存在有效子数组,返回 0;否则返回 result。
四、测试用例验证
测试用例 1:存在有效子数组
- 输入:target=7, nums=[2,3,1,2,4,3]
- 过程:
-
- i=0(nums [0]=2):sum=2 <7,不进入 while;
-
- i=1(nums [1]=3):sum=5 <7,不进入 while;
-
- i=2(nums [2]=1):sum=6 <7,不进入 while;
-
- i=3(nums [3]=2):sum=8 ≥7,进入 while:
-
-
- sub=3-0+1=4,result=4;sum=8-2=6,j=1;
-
-
-
- sum=6 <7,退出 while;
-
-
- i=4(nums [4]=4):sum=6+4=10 ≥7,进入 while:
-
-
- sub=4-1+1=4,result=min(4,4)=4;sum=10-3=7,j=2;
-
-
-
- sub=4-2+1=3,result=3;sum=7-1=6,j=3;
-
-
-
- sum=6 <7,退出 while;
-
-
- i=5(nums [5]=3):sum=6+3=9 ≥7,进入 while:
-
-
- sub=5-3+1=3,result=min(3,3)=3;sum=9-2=7,j=4;
-
-
-
- sub=5-4+1=2,result=2;sum=7-4=3,j=5;
-
-
-
- sum=3 <7,退出 while;
-
- 最终 result=2,符合预期。
测试用例 2:无有效子数组
- 输入:target=11, nums=[1,1,1,1,1,1,1,1]
- 过程:循环结束后 a=8 <11,返回 0,符合预期。
五、优化与注意事项
1. 代码优化点
- 变量 a 可省略:循环中若 result 始终未更新(即未找到有效窗口),说明无解。可将 result 初始化为 INT_MAX,循环结束后判断 result == INT_MAX 则返回 0,无需额外维护 a:
cpp
int result = INT_MAX; // 初始化为最大值
// ... 循环逻辑不变 ...
return result == INT_MAX ? 0 : result; // 简化判断
-
这样修改后,代码更简洁,减少不必要的变量存储。
-
- 边界条件注意
-
数组长度为 1:若 nums[0] ≥ target,返回 1;否则返回 0;
-
target 等于数组中某个元素:此时最小窗口长度为 1(例如 target=3, nums=[1,3,2],直接返回 1);
-
所有元素和恰好等于 target:此时最小窗口长度为数组长度。
-
六、算法总结
-
时间复杂度:O(n),左指针和右指针均最多遍历数组一次,无嵌套循环;
-
空间复杂度:O(1),仅使用常数个变量;
-
核心优势:相比暴力解法,滑动窗口避免了重复计算子数组和,大幅提升效率,是解决 "连续子数组和 / 长度" 类问题的经典方法。
-
滑动窗口的核心思路是 "动态调整窗口范围,维护有效状态",类似的题目还包括 "最长无重复子串""最小覆盖子串" 等,掌握这种思想可以举一反三,高效解决一系列数组区间问题。