持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
题目
- 分类:数组
- 难度(入门|中等|难):中等
- 209. 长度最小的子数组 - 力扣(LeetCode)
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
思路
使用滑动窗口, 本质上也是一个双指针方法.
-
前置思考: 必然要遍历整个数组, 才能找到最小的
-
补充条件: 超出的数组必须连续, 考虑滑动窗口方法.
-
问题关键: 两个指针的移动, start指针指窗口头, end指针指窗口尾, 如何移动两个指针?
-
end指针移动的逻辑: 由于必然要遍历一遍数组, 滑动窗口也只能在数组内部移动, 所以让end指针一直往数组末尾走, 不会回头, 当end指针走到尾, 整个数组就遍历完。
while(end<nums.length){ // end指针一直往数组尾走, 每走一步就要同步窗口内元素总和 sum += nums[end++]; ... } -
start指针移动的逻辑:
-
当end指针一直往前走, 带着滑动窗口也一直扩大, 接下来会有两种情况, end一直走到数组尾, sum也没超过target, 那么就根据要求结束方法, 返回0;
// 直接走这儿 return minlen == Integer.MAX_VALUE ? 0 : minlen; -
另一种情况是end指针走到数组中间某处, 此时sum>target, 那么就应当记录下此时窗口长度
end-start.
注意: 这里之所以是 end-start , 而不是 (end - start + 1) 是因为我们在求完sum后让end++了, 所以现在end指向滑动窗口尾部位置的后一位, 而不是滑动窗口的尾部位置
-
这时我们满足了第一个要求: 连续子数组之和大于target. 但此时还不一定满足第二个需求: 长度最小, 也可以理解为滑动窗口的长度最小. 所以此时应当考虑前移start指针.
-
start前移的活动规律是: 它不断前移, 每次移动都重新记录sum值, 而start每次前移, 就说明滑动窗口缩小了, 所以长度也要重新记录. 最终只会出现一种情况: 每次前移都减去一部分sum值, sum值不断减少至sum<target, 此时不满足第一个要求了, start停止前移.
-
所以start前移也应当在一个循环里, 停止循环的条件是:
sum<target
while(sum >= target){ minlen = Math.min(minlen, end-start); sum -= nums[start++]; } -
-
两个while循环的组合问题:
- 代码里存在两个循环, 第一个不断让end前移, 第二个不断让start前移.
- 在我们刚才的分析中, 让end前移, 以满足题目的第一个要求——"连续子数组", 而满足第二个要求——"子数组长度最小", 是建立在满足第一个要求基础上才能满足的,才能让start前移, 执行第二个循环。
- 这种关系决定了,两个循环是嵌套的,控制end的循环在外,控制start的循环在内。完整的代码看下文:
代码
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int start = 0;
int end = 0;
int sum = 0;
int minlen = Integer.MAX_VALUE;
while(end<nums.length){
sum+=nums[end++];
while(sum>=target){
minlen = Math.min(minlen, end-start);
sum-=nums[start++];
}
}
return minlen==Integer.MAX_VALUE?0:minlen;
}
}
总结
- 滑动窗口方法是双指针方法的变体,难度有上升,不会做非常合理!