数组 leetcode 209. 长度最小的子数组 (滑动窗口)

题目内容

给定一个含有 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方式.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值