滑动窗口题目:长度最小的子数组
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 进阶
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:长度最小的子数组
出处:209. 长度最小的子数组
难度
5 级
题目描述
要求
给定一个正整数数组 nums\texttt{nums}nums 和一个正整数 target\texttt{target}target,返回元素和大于等于 target\texttt{target}target 的连续子数组 [numsl,numsl+1,...,numsr-1,numsr]\texttt{[nums}_\texttt{l}\texttt{, nums}_\texttt{l + 1}\texttt{, ..., nums}_\texttt{r - 1}\texttt{, nums}_\texttt{r}\texttt{]}[numsl, numsl + 1, ..., numsr - 1, numsr] 的最小长度。如果不存在符合条件的子数组,返回 0\texttt{0}0。
示例
示例 1:
输入:target=7,nums=[2,3,1,2,4,3]\texttt{target = 7, nums = [2,3,1,2,4,3]}target = 7, nums = [2,3,1,2,4,3]
输出:2\texttt{2}2
解释:子数组 [4,3]\texttt{[4,3]}[4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target=4,nums=[1,4,4]\texttt{target = 4, nums = [1,4,4]}target = 4, nums = [1,4,4]
输出:1\texttt{1}1
示例 3:
输入:target=11,nums=[1,1,1,1,1,1,1,1]\texttt{target = 11, nums = [1,1,1,1,1,1,1,1]}target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0\texttt{0}0
数据范围
- 1≤target≤109\texttt{1} \le \texttt{target} \le \texttt{10}^\texttt{9}1≤target≤109
- 1≤nums.length≤105\texttt{1} \le \texttt{nums.length} \le \texttt{10}^\texttt{5}1≤nums.length≤105
- 1≤nums[i]≤105\texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{5}1≤nums[i]≤105
进阶
如果你已经实现 O(n)\texttt{O(n)}O(n) 时间复杂度的解法, 请尝试实现 O(nlogn)\texttt{O(n log n)}O(n log n) 时间复杂度的解法。
解法一
思路和算法
由于数组 nums\textit{nums}nums 中的元素都是正整数,因此对于任意子数组,在子数组的任意一端添加元素(即增加子数组的长度)后子数组中的元素和一定增加,在子数组的任意一端删除元素(即减少子数组的长度)后子数组中的元素和一定减少。
考虑数组 nums\textit{nums}nums 的两个不同下标 end1\textit{end}_1end1 和 end2\textit{end}_2end2,其中 end1<end2\textit{end}_1 < \textit{end}_2end1<end2,分别以这两个下标作为结束下标,寻找元素和大于等于 target\textit{target}target 的最短子数组,将这两个最短子数组的开始下标分别记为 start1\textit{start}_1start1 和 start2\textit{start}_2start2,则必有 start1≤start2\textit{start}_1 \le \textit{start}_2start1≤start2(否则对于任意 start2<start3≤start1\textit{start}_2 < \textit{start}_3 \le \textit{start}_1start2<start3≤start1,下标范围 [start3,end2][\textit{start}_3, \textit{end}_2][start3,end2] 的子数组的元素和大于等于 target\textit{target}target,且长度小于下标范围 [start2,end2][\textit{start}_2, \textit{end}_2][start2,end2] 的子数组)。
因此,可以使用变长滑动窗口寻找数组 nums\textit{nums}nums 中的元素和大于等于 target\textit{target}target 的最短子数组。用 [start,end][\textit{start}, \textit{end}][start,end] 表示滑动窗口,初始时 start=end=0\textit{start} = \textit{end} = 0start=end=0。将滑动窗口的右端点 end\textit{end}end 向右移动,移动过程中维护滑动窗口的左端点 start\textit{start}start,对于每个 end\textit{end}end 寻找元素和大于等于 target\textit{target}target 的最小滑动窗口。
用 sum\textit{sum}sum 表示滑动窗口中的元素和。对于每个右端点 end\textit{end}end,执行如下操作。
-
将 sum\textit{sum}sum 的值增加 nums[end]\textit{nums}[\textit{end}]nums[end]。
-
如果 sum≥target\textit{sum} \ge \textit{target}sum≥target,则当前滑动窗口 [start,end][\textit{start}, \textit{end}][start,end] 中的子数组为元素和大于等于 target\textit{target}target 的子数组,其长度为 end−start+1\textit{end} - \textit{start} + 1end−start+1,使用当前子数组的长度更新子数组的最小长度,然后将 sum\textit{sum}sum 的值减少 nums[start]\textit{nums}[\textit{start}]nums[start],将 start\textit{start}start 向右移动一位,重复该操作直到 sum<target\textit{sum} < \textit{target}sum<target。
遍历结束之后,可以得到数组 nums\textit{nums}nums 中的元素和大于等于 target\textit{target}target 的子数组的最小长度。
代码
class Solution {public int minSubArrayLen(int target, int[] nums) {int minLength = Integer.MAX_VALUE;int sum = 0;int start = 0, end = 0;int n = nums.length;while (end < n) {sum += nums[end];while (sum >= target) {minLength = Math.min(minLength, end - start + 1);sum -= nums[start];start++;}end++;}return minLength == Integer.MAX_VALUE ? 0 : minLength;}
}
复杂度分析
-
时间复杂度:O(n)O(n)O(n),其中 nnn 是数组 nums\textit{nums}nums 的长度。滑动窗口的左右端点最多各遍历数组 nums\textit{nums}nums 一次。
-
空间复杂度:O(1)O(1)O(1)。
解法二
思路和算法
由于数组 nums\textit{nums}nums 中的元素都是正整数,因此子数组元素和的最大值为整个数组的元素和。如果整个数组的元素和小于 target\textit{target}target,则不存在元素和大于等于 target\textit{target}target 的子数组,返回 000。如果整个数组的元素和大于等于 target\textit{target}target,则一定存在元素和大于等于 target\textit{target}target 的子数组。
用 kkk 表示数组 nums\textit{nums}nums 的元素和大于等于 target\textit{target}target 的子数组的最小长度。由于数组 nums\textit{nums}nums 中的元素都是正整数,因此一定存在长度为 k+1k + 1k+1 的子数组的元素和大于等于 target\textit{target}target(当 kkk 等于数组 nums\textit{nums}nums 的长度时例外),且一定不存在长度小于 kkk 的子数组的元素和大于等于 target\textit{target}target。因此,这道题是二分查找判定问题,需要找到最小长度。
当子数组长度 xxx 固定时,可以使用长度为 xxx 的定长滑动窗口遍历数组 nums\textit{nums}nums,得到每个长度为 xxx 的子数组的元素和,并判断是否存在长度为 xxx 且元素和大于等于 target\textit{target}target 的子数组。具体做法如下。
-
用 sum\textit{sum}sum 表示长度为 xxx 的子数组的元素和,初始时 sum\textit{sum}sum 等于最左侧的 xxx 个元素之和。
-
对于 x≤i<nx \le i < nx≤i<n,当子数组的下标范围从 [i−x,i−1][i - x, i - 1][i−x,i−1] 移动到 [i−x+1,i][i - x + 1, i][i−x+1,i] 时,nums[i−x]\textit{nums}[i - x]nums[i−x] 移出子数组,nums[i]\textit{nums}[i]nums[i] 移入子数组,因此 sum\textit{sum}sum 的变化是减少 nums[i−x]\textit{nums}[i - x]nums[i−x] 并增加 nums[i]\textit{nums}[i]nums[i]。
-
遍历所有的长度为 xxx 的子数组之后,即可得到每个长度为 xxx 的子数组的元素和,并判断是否存在长度为 xxx 且元素和大于等于 target\textit{target}target 的子数组。
用 low\textit{low}low 和 high\textit{high}high 分别表示二分查找的下界和上界。由于子数组至少有 111 个元素,因此 low\textit{low}low 的初始值等于 111;由于子数组的最大长度为整个数组的长度,因此 high\textit{high}high 的初始值等于数组 nums\textit{nums}nums 的长度。
每次查找时,取 mid\textit{mid}mid 为 low\textit{low}low 和 high\textit{high}high 的平均数向下取整,判断是否存在长度为 mid\textit{mid}mid 且元素和大于等于 target\textit{target}target 的子数组,执行如下操作。
-
如果存在长度为 mid\textit{mid}mid 且元素和大于等于 target\textit{target}target 的子数组,则最小长度 kkk 小于等于 mid\textit{mid}mid,因此在 [low,mid][\textit{low}, \textit{mid}][low,mid] 中继续查找。
-
如果不存在长度为 mid\textit{mid}mid 且元素和大于等于 target\textit{target}target 的子数组,则最小长度 kkk 大于 mid\textit{mid}mid,因此在 [mid+1,high][\textit{mid} + 1, \textit{high}][mid+1,high] 中继续查找。
当 low=high\textit{low} = \textit{high}low=high 时,查找结束,此时 low\textit{low}low 即为最小长度 kkk。
实现方面,由于只需要判断是否存在元素和大于等于 target\textit{target}target 的子数组,因此只要找到一个子数组的元素和大于等于 target\textit{target}target 即可提前返回。
代码
class Solution {public int minSubArrayLen(int target, int[] nums) {int total = Arrays.stream(nums).sum();if (total < target) {return 0;}int low = 1, high = nums.length;while (low < high) {int mid = low + (high - low) / 2;if (exists(target, nums, mid)) {high = mid;} else {low = mid + 1;}}return low;}public boolean exists(int target, int[] nums, int subarrayLen) {int sum = 0;int n = nums.length;for (int i = 0; i < subarrayLen; i++) {sum += nums[i];if (sum >= target) {return true;}}for (int i = subarrayLen; i < n; i++) {sum -= nums[i - subarrayLen];sum += nums[i];if (sum >= target) {return true;}}return false;}
}
复杂度分析
-
时间复杂度:O(nlogn)O(n \log n)O(nlogn),其中 nnn 是数组 nums\textit{nums}nums 的长度。当存在符合条件的子数组时,需要执行 O(logn)O(\log n)O(logn) 次二分查找,每次二分查找需要 O(n)O(n)O(n) 的时间遍历数组 nums\textit{nums}nums,时间复杂度是 O(nlogn)O(n \log n)O(nlogn)。
-
空间复杂度:O(1)O(1)O(1)。