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 <= 10^9

1 <= nums.length <= 10^5

1 <= nums[i] <= 10^5

进阶:

如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

V1-暴力解法

思路

我们直接从 step=1 的步长开始遍历,得到最后的结果。

[startIndex, endIndex] 中的所有元素累加,如果符合结果,则返回。

实现

public class T209_MinimumSizeSubarraySum {

    /**
     * 使用 slide window 实现
     *
     * 1. step 从 1 到 len
     *
     * @param target
     * @param nums
     * @return
     */
    public int minSubArrayLen(int target, int[] nums) {
        for(int step = 0; step < nums.length; step++) {
            for(int i = 0; i < nums.length - step; i++) {
                if(fitSum(nums, i, i+step, target)) {
                    return step+1;
                }
            }
        }

        return 0;
    }

    private boolean fitSum(int[] nums,
                        int startIndex,
                        int endIndex,
                           int target) {
        int sum = 0;
        for(int i = startIndex; i <= endIndex; i++) {
            sum += nums[i];

            if(sum >= target) {
                return true;
            }
        }

        return false;
    }

}

当然,这个会在 18/22 的时候超时。

V2-引入 cache

思路

我一开始在想,是不是因为每次计算 sum,都要从头开始计算导致的?

因为我们步长从小到大,那么 sum[0, i] = sum[0...i-1] + nums[i]。引入一个缓存,让计算 sum 的辅助度降低为 O(1).

实现

public class T209_MinimumSizeSubarraySumV2 {

    /**
     * 使用 slide window 实现
     *
     * 1. step 从 1 到 len
     *
     * 添加缓存,依然超时
     * @param target
     * @param nums
     * @return
     */
    public int minSubArrayLen(int target, int[] nums) {
        // 缓存,因为步长最短。
        Integer[][] cache = new Integer[nums.length][nums.length];

        for(int step = 0; step < nums.length; step++) {
            for(int i = 0; i < nums.length - step; i++) {
                int sum = calcSum(nums, i, i+step, cache);
                if(sum >= target) {
                    return step+1;
                }
            }
        }

        return 0;
    }

    private int calcSum(int[] nums,
                        int startIndex,
                        int endIndex,
                        Integer[][] cache) {
        int sumCache = 0;
        if(endIndex > 0) {
            Integer val = cache[startIndex][endIndex-1];
            if(val != null) {
                sumCache = val;
            }
            // 其他为0
        }

        int sum = sumCache + nums[endIndex];
        cache[startIndex][endIndex] = sum;
        return sum;
    }

}

不过因为测试的 case 数据量很大,导致内存限制。所以不可行。

V3-最短的另一种思路

思路

我们一开始是扩大步长的方式,然后逐个尝试。

这样会比较麻烦。

其实我们可以换一种思路:

(1)从 0 开始累加 sum,当 >= target 的时候,end 指标停下。如果全部加起来还不满足,那直接 false。

(2)另一个指针 start,开始从 0 往后,开始缩小对应的元素个数,到 <= target 结束。

这样可以再 [start, end] 这个范围内找到满足的结果,对应的下标差距就是结果。

实现

class Solution {
    
    public int minSubArrayLen(int s, int[] nums) {
        int start = 0;
        int end = 0;
        int sum = 0;
        int minLen = Integer.MAX_VALUE;

        while (end < nums.length) {
            while (end < nums.length && sum < s) {
                sum += nums[end++];
            }
            // 剪枝:全部的和不满足
            if (sum < s) {
                break;
            }

            // 开始尝试减少长度
            while (start < end && sum >= s) {
                sum -= nums[start++];
            }
            if (end - start + 1 < minLen) {
                minLen = end - start + 1;
            }
        }

        return minLen == Integer.MAX_VALUE ? 0 : minLen;
    }

}

参考资料

https://leetcode.com/problems/minimum-size-subarray-sum/description/

https://leetcode.cn/problems/minimum-size-subarray-sum/description/

https://leetcode.com/problems/minimum-size-subarray-sum/solutions/59103/two-ac-solutions-in-java-with-time-complexity-of-n-and-nlogn-with-explanation/