题目描述:
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-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 <= 109
- 1 <= nums.length <= 105
- 1 <= nums[i] <= 105
进阶: 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。
思路:
(1)暴力法
//暴力法
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int length = nums.length;
if (length == 0) return 0;
int min = length + 1;
for (int i = 0; i < length; i++){
int sum = 0;
for (int j = i; j < length; j++){
sum += nums[j];
if (sum >= target){
min = Math.min(min, j - i + 1);
break;
}
}
}
return min == (length + 1) ? 0 : min;
}
}
(2)二分法查找
我们申请一个临时数组 sums,其中 sums[i] 表示的是原数组 nums 前 i 个元素的和,题中说了 “给定一个含有 n 个 正整数的数组”,既然是正整数,那么相加的和会越来越大,也就是sums数组中的元素是递增的。
我们只需要找到sums[k]-sums[j]>=s,那么 k-j 就是满足的连续子数组,但不一定是最小的,所以我们要继续找,直到找到最小的为止。
怎么找呢,我们可以使用两个 for 循环来枚举,但这又和第一种暴力求解一样了,所以我们可以换种思路,求 sums[k]-sums[j]>=s 我们可以求sums[j]+s<=sums[k],那这样就好办了,因为数组sums中的元素是递增的,也就是排序的,我们只需要求出 sum[j]+s的值,然后使用二分法查找即可找到这个k。
参考文章:https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/javade-jie-fa-ji-bai-liao-9985de-yong-hu-by-sdwwld/
大佬太牛了,讲的比官方详细多了。
//二分法查找
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int length = nums.length;
if (length == 0) return 0;
int min = length + 1;
int[] sums = new int[length + 1];
//sums[i]表示数组nums的前i个元素和
// 题目说nums是正整数的数组,则sums[]一定是递增的
for (int i = 1; i <= length; i++) {
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 0; i <= length; i++) {
//只需要找到 sums[j]-sums[i]>=target,那么 j-i 就是满足的连续子数组的长度,但不一定是最小的
//求 sums[j]-sums[i]>=target 我们可以求 sums[i]+target<=sums[j]
int t = target + sums[i];
int j = Arrays.binarySearch(sums, t);//二分查找
if (j < 0) {
j = -j - 1;
}
if (j <= length) {
min = Math.min(min, j - i);
}
}
return min == (length + 1) ? 0 : min;
}
}
关于Arrays.binarySearch,以前我自己就写过一篇笔记,竟然忘了。。。赶紧复习复习www
Java学习笔记:Arrays类的binarySearch()方法
3.队列相加
public int minSubArrayLen(int s, int[] nums) {
int lo = 0, hi = 0, sum = 0, min = Integer.MAX_VALUE;
while (hi < nums.length) {
sum += nums[hi++];
while (sum >= s) {
min = Math.min(min, hi - lo);
sum -= nums[lo++];
}
}
return min == Integer.MAX_VALUE ? 0 : min;
}
4.队列相减
public int minSubArrayLen(int s, int[] nums) {
int lo = 0, hi = 0, min = Integer.MAX_VALUE;
while (hi < nums.length) {
s -= nums[hi++];
while (s <= 0) {
min = Math.min(min, hi - lo);
s += nums[lo++];
}
}
return min == Integer.MAX_VALUE ? 0 : min;
}
5.直接使用窗口
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;
}