题目:LCR 008. 长度最小的子数组
给定一个含有 n ****个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., nums r-1, numsr] ,并返回其长度 。 如果不存在符合条件的子数组,返回 0 。
示例 1:
输入: target = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入: target = 4, nums = [1,4,4]
输出: 1
示例 3:
输入: target = 11, nums = [1,1,1,1,1,1,1,1]
输出: 0
提示:
1 <= target <= 1091 <= nums.length <= 1051 <= nums[i] <= 105
进阶:
- 如果你已经实现
O(n)时间复杂度的解法, 请尝试设计一个O(n log(n))时间复杂度的解法。
解答:
方法一:暴力求解(超时)
1)首先,拿到这道题的第一反应,直接遍历每个元素作为开头的符合条件的最小连续子数组,记录最小连续子数组的值。但是使用该方法的最坏时间复杂度是O(n^2)。
代码:
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n=nums.length;
int minLength= Integer.MAX_VALUE;
for(int i=0;i<n;i++){
int sum=0;
for(int j=i;j<n;j++){
sum+=nums[j];
if(sum>=target){
minLength=Math.min(minLength,j-i+1);
}
}
}
return minLength==Integer.MAX_VALUE?0:minLength;
}
}
方法二:滑动窗口
2)方法一超时!简单的脑细胞用完之后,稍微调用一下高级一点的脑细胞,很容易想到:诶!这不滑动窗口吗?在时间复杂度为O(n)解决该问题。定义两个指针表示滑动窗口的两个边界,滑动窗口右边界end向后累加元素,当元素值大于等于target后,比较当前的窗口大小是否更小,start也不断向后减去元素,直到sum不在大于等于target为止。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
int minLength = Integer.MAX_VALUE;
int start = 0, end = 0, sum = 0;
while (end < n) {
sum += nums[end++];
while (sum >= target) {
minLength = Math.min(minLength, end - start);
sum -= nums[start++];
}
}
return minLength == Integer.MAX_VALUE ? 0 : minLength;
}
}
方法3:前缀和+二分查找
题解方法,为了使用二分查找,需要额外创建一个数组 sums 用于存储数组 nums 的前缀和,其中 preSum[i] 表示从 nums[0] 到 nums[i−1] 的元素和。得到前缀和之后,对于每个开始下标 i,可通过二分查找得到大于或等于 i 的最小下标 bound,使得 preSum[bound]−preSum[i−1]≥target,并更新子数组的最小长度(此时子数组的长度是 bound−(i−1))。
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
if (n == 0)
return 0;
int res = Integer.MAX_VALUE;
int[] preSum = new int[n + 1];
for (int i = 1; i <= n; i++) {
preSum[i] = preSum[i - 1] + nums[i - 1];
}
for (int i = 1; i <= n; i++) {
int t = target + preSum[i - 1];
int bound = find(preSum, t);// 找到第一个大于等于t的坐标
if (bound <= n) {
res = Math.min(res, bound - i + 1);
}
}
return res == Integer.MAX_VALUE ? 0 : res;
}
public int find(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
}
}