剑指 Offer II 008. 和大于等于 target 的最短子数组(209. 长度最小的子数组)
题目:
思路:
【1】利用队列+单次遍历整个数组的方式来处理(这种就是模仿滑动窗口的思维)
首先要了解到本质:
因为需要知道 nums 中是否有 连续子数组 (这个要求很重点,是做窗口的前提)
能够使元素的汇总sum >= target
所以可以利用窗口的思维
而这个窗口,你可以理解为算是另一个存储元素的地方
因为你可以采用链表或者队列来做窗口也行
又由于对nums不会进行变更,采用双指针来作为边界,作为窗口也行
因为 target = 20 , nums = [1,2,3,4,5,15,10,21];
//用双指针做示例left = right = 0;
如果一开始的话
sum += nums[right++]; 当right = 5的时候nums[5] = 15,
此时 sum = 30 > 20 ,所以此时长度首先为right - left + 1 = 6,
这时候就要从左边吐数据了,先吐1,然后29>20,最小长度记为5
再吐2,以此类推
再吐3,以此类推
直到sum < target 或者 left == right
【2】滑动窗口的做法
代码展示:
滑动窗口的做法:
//时间4 ms击败8.46% //内存48.7 MB击败62.20% //这种算是通用的吧,利用二分缩小窗口大小,但是本质上还是会存在比较多无用的操作 class Solution { public int minSubArrayLen(int s, int[] nums) { int lo = 1, hi = nums.length, min = 0; while (lo <= hi) { int mid = (lo + hi) >> 1; if (windowExist(mid, nums, s)) { hi = mid - 1;//找到就缩小窗口的大小 min = mid;//如果找到就记录下来 } else lo = mid + 1;//没找到就扩大窗口的大小 } return min; } //size窗口的大小 private boolean windowExist(int size, int[] nums, int s) { int sum = 0; for (int i = 0; i < nums.length; i++) { if (i >= size) sum -= nums[i - size]; sum += nums[i]; if (sum >= s) return true; } return false; } }
利用队列+单次遍历整个数组的方式来处理的方式:
//时间6 ms击败6.84% //内存51.8 MB击败5.1% class Solution { public int minSubArrayLen(int target, int[] nums) { LinkedList<Integer> queue = new LinkedList<>(); int sum = 0 , res = Integer.MAX_VALUE; for (int x : nums){ sum += x; queue.add(x); while (sum >= target){ res = Math.min(res,queue.size()); sum -= queue.pollFirst(); } } return res == Integer.MAX_VALUE ? 0 : res; } } //进一步采用双指针的方式替代队列(明显空间复杂度下降了,而且在这个过程中不用统计队列的大小) //时间1 ms击败100% //内存41.4 MB击败43.27% class Solution { public int minSubArrayLen(int target, int[] nums) { int sum = 0 , left = 0 , res = Integer.MAX_VALUE; for (int i = 0 ; i < nums.length; i++){ sum += nums[i]; while (sum >= target){ res = Math.min(res,i-left+1); sum -= nums[left++]; } } return res == Integer.MAX_VALUE ? 0 : res; } }