题目内容
给定一个含有 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)) 时间复杂度的解法。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-size-subarray-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
java解答
/*滑动窗口思想,没有比target大前,右边向右扩展,大于的时候记录此时的数据长度,然后减去左边的看是否还能大于,能就继续减,不能也减,,然后右边继续扩展,时刻记录满足条件的长度,保存最小值*/
class Solution {
public int minSubArrayLen(int target, int[] nums) {
//滑动窗口的左右两个边界
int left=0,right=0;
//统计窗口中所有数字的长度
int sum=0;
int len=0;
for(right=0;right<nums.length;right++){
//窗口向右扩展1个
sum=sum+nums[right];
//扩展后长度如果大于等于target然后开始统计其长度,开始让左边长度缩减
if(sum>=target){
//如果是第一超过target,给len赋当前窗口的长度
if(len==0){
len=right-left+1;
}
// if(len>right-left+1){
// len=right-left+1;
// }
//开始让窗口左边界向右缩,即使缩进后sum不大于等于target,left也不用再减回去了,因为窗口这时候在不满足的时候开始同时向右移动
while(sum>=target){
//统计向右缩后窗口内数的和
sum=sum-nums[left];
//左边界向右加1
left++;
}
// left--; 加上会报错,没必要,窗口两边最后肯定要移动
//right-left+2是出来while后当前段的最小是,看是否需要更新len
if(len>right-left+2){
//此时的left已经向右多移动了一个,窗口间长度是right-left+1,但是left提前向右移了,所以还要再加个1
len=right-left+2;
}
}
}
return len;
}
}
java完整源代码,可在编译器中执行,方便调试,我把注释删除了,可以尝试自己理解下O(n)
public class L209 {
public static void main(String[] args) {
int [] nums=new int[]{1,2,3,4,5};
int i = minSubArrayLen(11, nums);
System.out.println(i);
}
public static int minSubArrayLen(int target, int[] nums) {
int left=0,right=0;
int sum=0;
int len=0;
for(right=0;right<nums.length;right++){
sum=sum+nums[right];
if(sum>=target){
if(len==0){
len=right-left+1;
}
while(sum>=target){
sum=sum-nums[left];
left++;
}
if(len>right-left+2){
len=right-left+2;
}
}
}
return len;
}
}
看了官方答案思路,自己也写了,和官方差不多,反而让我知道了java二分的源代码(二分法+前缀和)O(nlogn)
class Solution {
public int minSubArrayLen(int target, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int ans = Integer.MAX_VALUE;
int[] sums = new int[n];
//sum[0]代表0下标包括他前面的和
// 以此类推
sums[0]=nums[0];
for (int i = 1; i < n; i++) {
sums[i] = sums[i - 1] + nums[i];
}
for (int i = 0; i < n; i++) {
int key=target;
if(i>=1){
key = target + sums[i-1];
}
//java函数二分返回的是什么
int bound = Arrays.binarySearch(sums, key);
if (bound < 0) {
//这里为什么是这样的?
bound = -bound - 1;
}
//知道这里是为什么不能等于么?
if (bound <n) {
ans = Math.min(ans, bound - i+1);
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}
- 想知道上面你的问题,跟我一起来看看二分源码,你就明白了,也可以自己一步一步断点调试调试你就明白了
public static int binarySearch(int[] a, int key) {
//这里有边界传的是数组的长度
return binarySearch0(a, 0, a.length, key);
}
// Like public version, but without range checks.
private static int binarySearch0(int[] a, int fromIndex, int toIndex,
int key) {
int low = fromIndex;
//这里high变成了最右边元素对应的下标
int high = toIndex - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int midVal = a[mid];
if (midVal < key)
low = mid + 1;
else if (midVal > key)
high = mid - 1;
else
return mid; // key found
}
//返回的是插入的下标位置(可能不符合规矩,出现负数),low的大小可能是在数组中没有的下标,比如-1,或者刚好是数组的长度的大小
return -(low + 1); // key not found.
}
- return -(low + 1); // key not found.
- if (bound < 0) --> bound = -bound - 1;
- 重点明白这两个,如果是low是-1的话,bound那么return 0,这时候进不了if,low不是-1的话,位置bound小于0,进了bound后就会变成low,也就是即将插入的位置(注意:可能这个位置中超出数组的范围) 所以就有了下一句
//超出范围的进不到if里面,没超出的就可以计算他的长度了,就是插入的位置减去左边界然后加1
if (bound < n) {
ans = Math.min(ans, bound - i+1);
}
总结
其实感觉滑动窗口也属与双指针,感觉所有事情都是想通的,没有白下的功夫,网络中为了提高传递包的效率,用了滑动窗口来提高速度,刚好可以用来解决生活中的许多问题,而且发现断点调试是非常好的解决bug方式.