每天一道编程题

45 阅读3分钟

题目:LCR 008. 长度最小的子数组

给定一个含有 n ****个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., nums r-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)) 时间复杂度的解法。

解答:

方法一:暴力求解(超时)

1)首先,拿到这道题的第一反应,直接遍历每个元素作为开头的符合条件的最小连续子数组,记录最小连续子数组的值。但是使用该方法的最坏时间复杂度是O(n^2)。

代码:

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int n=nums.length;
        int minLength= Integer.MAX_VALUE;
        for(int i=0;i<n;i++){
            int sum=0;
            for(int j=i;j<n;j++){
                sum+=nums[j];
                if(sum>=target){
                    minLength=Math.min(minLength,j-i+1);
                }
            }
        }
        return minLength==Integer.MAX_VALUE?0:minLength;
    }
}

方法二:滑动窗口

2)方法一超时!简单的脑细胞用完之后,稍微调用一下高级一点的脑细胞,很容易想到:诶!这不滑动窗口吗?在时间复杂度为O(n)解决该问题。定义两个指针表示滑动窗口的两个边界,滑动窗口右边界end向后累加元素,当元素值大于等于target后,比较当前的窗口大小是否更小,start也不断向后减去元素,直到sum不在大于等于target为止。

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

方法3:前缀和+二分查找

题解方法,为了使用二分查找,需要额外创建一个数组 sums 用于存储数组 nums 的前缀和,其中 preSum[i] 表示从 nums[0] 到 nums[i−1] 的元素和。得到前缀和之后,对于每个开始下标 i,可通过二分查找得到大于或等于 i 的最小下标 bound,使得 preSum[bound]−preSum[i−1]≥target,并更新子数组的最小长度(此时子数组的长度是 bound−(i−1))。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int n = nums.length;
        if (n == 0)
            return 0;
        int res = Integer.MAX_VALUE;
        int[] preSum = new int[n + 1];
        for (int i = 1; i <= n; i++) {
            preSum[i] = preSum[i - 1] + nums[i - 1];
        }
        for (int i = 1; i <= n; i++) {
            int t = target + preSum[i - 1];
            int bound = find(preSum, t);// 找到第一个大于等于t的坐标
            if (bound <= n) {
                res = Math.min(res, bound - i + 1);
            }
        }
        return res == Integer.MAX_VALUE ? 0 : res;
    }

    public int find(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] >= target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}