LeetCode - 209. Minimum Size Subarray Sum(滑动窗口)

LeetCode - 209. Minimum Size Subarray Sum(滑动窗口)

  • 暴力O(N3),超时
  • 暴力优化O(N2)
  • 二分O(N*logN)
  • 滑动窗口O(N)

题目链接
题目

在这里插入图片描述

暴力O(N3),超时

可以枚举两个边界LR,然后计算[L,R]之间的和,然后判断是否>=sum ,并记录最小值即可。

class Solution {
    // tle 超时
    public int minSubArrayLen(int s, int[] nums) {
        if (nums == null || nums.length == 0)
            return 0;
        int res = nums.length + 1;
        for (int l = 0; l < nums.length; l++) {
            for (int r = l; r < nums.length; r++) {
                int sum = 0;
                for (int k = l; k <= r; k++)
                    sum += nums[k];
                if (sum >= s)
                    res = Math.min(res, r - l + 1);
            }
        }
        if (res == nums.length + 1)
            return 0;
        return res;
    }
}

暴力优化O(N2)

可以先计算出从0到每个位置i的和保存在一个sums数组中,然后枚举边界的时候,就可以直接取sums数组中的值相减即可,但是要注意:

  • 这里尽量不要用sums[i]表示[0,i]内的和;
  • 因为等下如果要计算[L,R]内的和,要使用sums[R] - sums[L-1],这样的话对于0位置就不好处理,所以使用sums[i]表示[0,i-1]之间的和,这样方便一点;
class Solution {
    // O(n^2)
    public int minSubArrayLen(int s, int[] nums) {
        if (nums == null || nums.length == 0)
            return 0;
        int res = nums.length + 1;
        int[] sums = new int[nums.length + 1];        // sums[i]存放nums[0...i-1]的和  (最好不要sums[i]存放0...i的和,不好处理0位置)
        sums[0] = 0; // 0~-1的和 
        for (int i = 1; i <= nums.length; i++)
            sums[i] = sums[i - 1] + nums[i - 1];

        for (int l = 0; l < nums.length; l++) {
            for (int r = l; r < nums.length; r++) {
                if (sums[r + 1] - sums[l] >= s)  // 使用sums[r+1] - sums[l] 快速获得nums[l...r]的和
                    res = Math.min(res, r - l + 1);
            }
        }
        if (res == nums.length + 1)
            return 0;
        return res;
    }
}

二分O(N*logN)

还是上面的思路,不同的是:

  • 我们可以利用二分查找求一个数组中>=key的位置,而我们的题目就是要求>=s的位置,所以对于左边界L,我们可以利用二分查找去查找第一个>=sum[l]+s的位置,这时就是我们要找的右边界R
  • 不过要注意: 我们每一个枚举的L,在sums数组查找到的R,其实是sum[R+1]位置代表的是[0,R]的和,所以要注意res = Math.min(res,R-L),而不是R-L+1

二分查找的几种变形请看这篇博客

class Solution {
    //ologn
    public int minSubArrayLen(int s, int[] nums) {
        if (nums == null || nums.length == 0)
            return 0;
        int res = nums.length + 1;
        int[] sums = new int[nums.length + 1];
        sums[0] = 0;
        for (int i = 1; i <= nums.length; i++)
            sums[i] = sums[i - 1] + nums[i - 1];
        for (int l = 0; l < nums.length; l++) {//必须从0开始 比如  s = 6,nums = {1,2,3}
            int r = firstLargeEqual(sums, sums[l] + s);
            if (r != sums.length)
                res = Math.min(res, r - l);      //注意这里不是r-l+1 ,因为寻找到的r实际上是r+1
        }
        if (res == nums.length + 1)
            return 0;
        return res;
    }

    public int firstLargeEqual(int[] arr, int key) {// 寻找第一个>= key的,不存在就返回arr.length
        int L = 0, R = arr.length - 1;
        int mid;
        while (L <= R) {
            mid = L + (R - L) / 2;
            if (arr[mid] >= key)
                R = mid - 1;
            else
                L = mid + 1;
        }
        return L;
    }
}

滑动窗口O(N)

滑动窗口的思想很简单,一直维护窗口的数:

  • 如果当前窗口内的和sum < s我们就往右边扩一个位置,并且维护窗口的和sum的值,但是要考虑R已经到达边界的情况,此时我们可以break了,因为就算L再往右边,也没用,因为此时sum < s
  • 否则我们的窗口就左边缩一个,并且继续维护sum
  • 然后我们要做的就是不断的记录窗口的长度R - L + 1的最小值;
class Solution {
    //O(n)
    public int minSubArrayLen(int s, int[] nums) {
        int L = 0, R = -1;  //一开始窗口内没有数
        int sum = 0;
        int res = nums.length + 1; //不可能的答案
        while (R < nums.length) {
            if (sum < s) {  //这里写成sum <= s也可以 ,看下面的方法
                if (++R == nums.length) break; //已经扩到最后一个数,可以退出了,因为此时已经sum < s,所以L你也更加不需要往右边扩了
                sum += nums[R];
            } else  // sum >= s
                sum -= nums[L++];

            if (sum >= s)
                res = Math.min(res, R - L + 1);
        }
        if (res == nums.length + 1)
            return 0;
        return res;
    }
}

上面的sum < s也可以写成sum <= s,效果是一样的:

class Solution {
    //O(n)
    public int minSubArrayLen(int s, int[] nums) {
        int L = 0, R = -1;  //一开始窗口内没有数
        int sum = 0;
        int res = nums.length + 1; //不可能的答案
        while (R < nums.length) {
            if (sum <= s) { //这里sum <= s也可以,因为下面已经判断了sum >= s就计算
                if (++R == nums.length) break;
                sum += nums[R];
            } else  // sum > s
                sum -= nums[L++];

            if (sum >= s)
                res = Math.min(res, R - L + 1);
        }
        if (res == nums.length + 1)
            return 0;
        return res;
    }
}

同时,也可以一开始窗口内有一个数,但是这样的话,要先维护res的值,然后再更新窗口:

class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        if (nums == null || nums.length == 0)
            return 0;
        int L = 0, R = 0;  //一开始窗口有一个数
        int sum = nums[0];
        int res = nums.length + 1;
        while (R < nums.length) {
            if (sum >= s) //这个必须放到上面,因为此时窗口已经有了一个数了
                res = Math.min(res, R - L + 1);
            if (sum < s) {
                if (++R == nums.length) break;
                sum += nums[R];
            } else  // sum >= s
                sum -= nums[L++];
        }
        if (res == nums.length + 1)
            return 0;
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值