0
点赞
收藏
分享

微信扫一扫

Java描述 LeetCode,209. Minimum Size Subarray Sum 长度最小的子数组之和,滑动窗口;前缀和+二分查找;

guanguans 2022-01-08 阅读 28

1-1:题目描述

Given an array of positive integers nums and a positive integer target, return the minimal length of a contiguous subarray [numsl, numsl+1, …, numsr-1, numsr] of which the sum is greater than or equal to target. If there is no such subarray, return 0 instead.

Example 1:

Input: target = 7, nums = [2,3,1,2,4,3]
Output: 2
Explanation: The subarray [4,3] has the minimal length under the problem constraint.

Example 2:

Input: target = 4, nums = [1,4,4]
Output: 1

Example 3:

Input: target = 11, nums = [1,1,1,1,1,1,1,1]
Output: 0

Constraints:

1 <= target <= 109
1 <= nums.length <= 105
1 <= nums[i] <= 105

Follow up: If you have figured out the O(n) solution, try coding another solution of which the time complexity is O(n log(n)).

题目理解:
在一个数组中间找到最小的数组,它的和=target就行。

1-2:滑动窗口解法

在这里插入图片描述
☘️算法流程:
核心思想是用一个可伸缩的滑动窗口去控制子数组的大小。整个窗口是左闭右闭型,[start, end],每次这种范围型的题目,一定要先自己想好自己的范围在哪?区间开闭? start控制窗口的头,end控制窗口的尾,每次start都往前面推一个,代表窗口在移动,end控制窗口的值的总和大小,一旦窗口内的和超过了target,end就结束,数组的长度end - start + 1。

☘️整体的思路并不难,难就难在对边界的掌握。我是没有能力一步就写到位的,只能通过debug去多次尝试。

public static int minSubArrayLen(int target, int[] nums) {
    int n = nums.length;
    int minLength = Integer.MAX_VALUE;
    int start = 0;
    int end = -1;
    int sum = 0;
    while (start < n) {
        while (sum < target && end != n - 1) {
            end++;
            sum += nums[end];
        }
        if (sum >= target) {
            minLength = Math.min(minLength, end - start + 1);
        }
        sum -= nums[start];
        start++;
    }
    return minLength == Integer.MAX_VALUE ? 0 : minLength;
}

时间复杂度O(N)
空间复杂度O(1)

☘️答案的思路和我的似乎不太一样,它是维持一个sum和,每次都通过end往sum里添加数,当达到target的时候,就通过start对这个窗口进行瘦身,把它变成和刚好<target,只要再来几个数就能满足和大于target。不明白可以看一下官方解答的图,其实和上面的大同小异。代码如下:

public static int minSubArrayLen2(int target, int[] nums) {
    int n = nums.length;
    if (n == 0) {
        return 0;
    }
    int minLength = Integer.MAX_VALUE;
    int start = 0;
    int end = 0;
    int sum = 0;
    while (end < n) {
        sum += nums[end];
        while (sum >= target) {
            minLength = Math.min(minLength, end - start + 1);
            sum -= nums[start];
            start++;
        }
        end++;
    }
    return minLength == Integer.MAX_VALUE ? 0 : minLength;
}
时间复杂度O(N)
空间复杂度O(1)

1-3:前缀和+二分查找

☘️我这里的和答案有一点区别,我这里前缀和是啥呢?prefix[i] = nums[0:i]之和,左闭区间,右闭区间。比如prefix[1] = nums[0] + nums[1];

☘️我们先给出一般情况下的示意图:
在这里插入图片描述
☘️由于nums数组中的值都是整数,所以前缀数组必然是递增的,这样就可以找到符合要求的bound,什么样的bound符合要求,在上面写了。所以整个算法有这样几个步骤:

  • 填充prefix前缀和数组
  • 从前缀和第一个位置0开始,二分查找符合要求的bound,并且计算数组区间长度加以更新。
public static int minSubArrayLen3(int target, int[] nums) {
    int n = nums.length;
    if (n == 0) {
        return 0;
    }
    int minLength = Integer.MAX_VALUE;
    int[] prefix = new int[n]; // prefix[i] : nums[0:i] 之和
    int sum = 0;
    // fill prefix[]
    for (int i = 0; i < n; i++) {
        sum += nums[i];
        prefix[i] = sum;
    }
    for (int i = 0; i < n; i++) {
        int val = (i == 0 ? target : target + prefix[i - 1]);
        int bound = Arrays.binarySearch(prefix, val); // bound = -insertion point - 1
        // can't find the val
        if (bound < 0) {
            bound = -bound - 1;
        }
        // if find the val, the scope of bound is from 0 to n-1. [0:n-1]
        if (bound <= n - 1) {
            minLength = Math.min(minLength, bound - i + 1);
        }
    }
    return minLength == Integer.MAX_VALUE ? 0 : minLength;
}
时间复杂度O(nlogn)
空间复杂度O(n)

☘️这里要特别注意java的二分查找,如果找到返回目标的index,如果找不到,就返回-该插入的位置的索引-1。为啥这样呢?比如0的时候,不-1就不是负号了,秒啊!所以<0就是没找到。然后再计算出符合要求的bound,这里的“插入的位置的索引”就是刚好前缀和>target的值。可以debug试试,你就知道我在说啥了。比如:

int[] nums = new int[]{2, 3, 1, 2, 4, 3};
int target = 7;
举报

相关推荐

0 条评论