209. Minimum Size Subarray Sum

题目描述和难度

  • 题目描述:

给定一个含有 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的子数组如果不存在符合条件的子数组,返回 0。

示例: 

输入: s = 7, nums = [2,3,1,2,4,3]
输出: 2
解释: 子数组 [4,3] 是该条件下的长度最小的子数组。

进阶:

如果你已经完成了O(n) 时间复杂度的解法, 请尝试 O(n log n) 时间复杂度的解法。

思路分析

求解关键: 思路一:使用二分查找 为什么能使用二分查找?“二分查找”不是要求数组是有序的吗?
“数组是正整数”这个条件在使用二分法解决这道问题中是至关重要的。因为“数组是正整数”,所以前缀和数组一定是严格增加的。 任意区间和可以通过前缀和数组得到,这是我们常见的一种做法。 起点固定的时候,区间越长,区间和越大。

思路二:使用滑动窗口的技巧来完成,要看过一遍整个数组的元素,所以时间复杂度是 $O(n)$。

因为要求的是满足区间和 >= s 的最小子区间的长度,因此,我们从左向右进行扫描。

  • 当区间和小于 s 的时候,右区间的端点向右扩展,这一点依赖外层循环的遍历就可以完成;
  • 一旦区间和大于等于 s,尝试一步一步缩小左区间端点,看看是否能得到一个更短的区间,满足区间和 >=s,这一步通过一个内层循环实现。

参考解答

参考解答1:构造前缀和数组,使用二分查找法。

public class Solution {

    public int minSubArrayLen(int s, int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        // 构造前缀和数组
        // 因为 nums 全都是正整数,因此 preSum 严格单调增加
        int[] preSum = new int[len];
        preSum[0] = nums[0];
        for (int i = 1; i < len; i++) {
            preSum[i] = preSum[i - 1] + nums[i];
        }
        // 因为前缀和数组严格单调增加,因此我们可以使用二分查找算法
        // 最后一位没有下一位了,所以外层遍历到最后一位的前一位就可以了
        int ret = len + 1;
        for (int i = 0; i < len - 1; i++) {
            // 计算区间和
            int l = i;
            int r = len - 1;
            // 设置成一个比较大的数,但是这个数有下界
            // i 的最大值是 len-2,
            // ans - i + 1 >= len + 1
            // ans >= i + len = 2 * len -2
            int ans = 2 * len - 2;
            // int ans = 2 * len - 1; 能通过
            // int ans = 2 * len - 3; 不能通过
            // 退出循环的条件是 l > r
            while (l <= r) {
                int mid = l + (r - l) / 2;
                // 计算一下区间和,找一个位置,使得这个位置到索引 i 的区间和为 s
                // 13 14 15 17 19 20
                int segmentSum = preSum[mid] - (i == 0 ? 0 : preSum[i - 1]);
                if (segmentSum >= s) {
                    ans = mid;
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
            ret = Integer.min(ans - i + 1, ret);
        }
        if (ret == len + 1) {
            return 0;
        }
        return ret;
    }
}

参考解答2:使用滑动窗口

public class Solution {

    public int minSubArrayLen(int s, int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        int l = 0;
        int r = -1;
        // 注意1:既然是求最小的长度,初始值应该设置成一个不可能达到的上限
        int minSubArrayLen = len + 1;
        int sum = 0;
        for (int i = 0; i < len; i++) {
            sum += nums[i];
            r++;
            // 注意2:这里的 = 号不要忘记了
            while (sum >= s) {
                minSubArrayLen = Integer.min(minSubArrayLen, r - l + 1);
                sum -= nums[l];
                l++;
            }
        }
        // 如果全部数组元素加起来都 <s ,即 minSubArrayLen 的值没有被更新,根据题意,返回 0
        if (minSubArrayLen == len + 1) {
            return 0;
        }
        return minSubArrayLen;
    }

    // 与上面的写法相同,只是边界条件设置不一样
    public int minSubArrayLen1(int s, int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        int l = 0;
        int r = 0;
        int minSubArrayLen = len + 1;
        int segmentSum = 0;
        for (int num : nums) {
            segmentSum += num;
            r++;
            // 注意:根据题意"找出该数组中满足其和 ≥ s 的长度最小的子数组"
            // 注意这个边界条件
            while (segmentSum >= s) {
                minSubArrayLen = Integer.min(minSubArrayLen, r - l);
                segmentSum -= nums[l];
                l++;
            }
        }
        if (minSubArrayLen == len + 1) {
            return 0;
        }
        return minSubArrayLen;
    }

    // 3 种写法本质上都是一样:滑动窗口
    public int minSubArrayLen2(int s, int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return 0;
        }
        int l = 0;
        int segmentSum = 0;
        int minSubArrayLen = len + 1;
        for (int i = 0; i < len; i++) {

            segmentSum += nums[i];
            while (segmentSum >= s) {
                minSubArrayLen = Integer.min(minSubArrayLen, i - l + 1);
                segmentSum -= nums[l];
                l++;
            }
        }
        if (minSubArrayLen == len + 1) {
            return 0;
        }
        return minSubArrayLen;
    }
}

本篇文章的地址为 https://liweiwei1419.github.io/leetcode-solution/leetcode-0209-minimum-size-subarray-sum ,如果我的题解有错误,或者您有更好的解法,欢迎您告诉我 liweiwei1419@gmail.com