LeetCode medium 209. 长度最小的子数组

这篇博客介绍了如何利用滑动窗口和二分查找算法解决寻找数组中和大于等于目标值的最小子数组长度的问题。提供了暴力法、二分查找法、队列相加和队列相减等多种解法,并详细解析了每种方法的思路,特别是二分查找法,通过维护一个前缀和数组,利用二分查找优化了时间复杂度。
摘要由CSDN通过智能技术生成

题目描述:

给定一个含有 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 <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

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

思路:
(1)暴力法

//暴力法
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int length = nums.length;
        if (length == 0) return 0;
        int min = length + 1;
        for (int i = 0; i < length; i++){
            int sum = 0;
            for (int j = i; j < length; j++){
                sum += nums[j];
                if (sum >= target){
                    min = Math.min(min, j - i + 1);
                    break;
                }
            }
        }
        return min == (length + 1) ? 0 : min;
    }
}

(2)二分法查找

我们申请一个临时数组 sums,其中 sums[i] 表示的是原数组 nums 前 i 个元素的和,题中说了 “给定一个含有 n 个 正整数的数组”,既然是正整数,那么相加的和会越来越大,也就是sums数组中的元素是递增的。
我们只需要找到sums[k]-sums[j]>=s,那么 k-j 就是满足的连续子数组,但不一定是最小的,所以我们要继续找,直到找到最小的为止。
怎么找呢,我们可以使用两个 for 循环来枚举,但这又和第一种暴力求解一样了,所以我们可以换种思路,求 sums[k]-sums[j]>=s 我们可以求sums[j]+s<=sums[k],那这样就好办了,因为数组sums中的元素是递增的,也就是排序的,我们只需要求出 sum[j]+s的值,然后使用二分法查找即可找到这个k。

参考文章:https://leetcode-cn.com/problems/minimum-size-subarray-sum/solution/javade-jie-fa-ji-bai-liao-9985de-yong-hu-by-sdwwld/
大佬太牛了,讲的比官方详细多了。

//二分法查找
class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int length = nums.length;
        if (length == 0) return 0;
        int min = length + 1;

        int[] sums = new int[length + 1];
        //sums[i]表示数组nums的前i个元素和
        // 题目说nums是正整数的数组,则sums[]一定是递增的
        for (int i = 1; i <= length; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }

        for (int i = 0; i <= length; i++) {
            //只需要找到 sums[j]-sums[i]>=target,那么 j-i 就是满足的连续子数组的长度,但不一定是最小的
            //求 sums[j]-sums[i]>=target 我们可以求 sums[i]+target<=sums[j]
            int t = target + sums[i];
            int j = Arrays.binarySearch(sums, t);//二分查找
            if (j < 0) {
                j = -j - 1;
            }
            if (j <= length) {
                min = Math.min(min, j - i);
            }
        }
        return min == (length + 1) ? 0 : min;
    }
}

关于Arrays.binarySearch,以前我自己就写过一篇笔记,竟然忘了。。。赶紧复习复习www
Java学习笔记:Arrays类的binarySearch()方法

3.队列相加

    public int minSubArrayLen(int s, int[] nums) {
        int lo = 0, hi = 0, sum = 0, min = Integer.MAX_VALUE;
        while (hi < nums.length) {
            sum += nums[hi++];
            while (sum >= s) {
                min = Math.min(min, hi - lo);
                sum -= nums[lo++];
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }

4.队列相减

    public int minSubArrayLen(int s, int[] nums) {
        int lo = 0, hi = 0, min = Integer.MAX_VALUE;
        while (hi < nums.length) {
            s -= nums[hi++];
            while (s <= 0) {
                min = Math.min(min, hi - lo);
                s += nums[lo++];
            }
        }
        return min == Integer.MAX_VALUE ? 0 : min;
    }

5.直接使用窗口

    public int minSubArrayLen(int s, int[] nums) {
        int lo = 1, hi = nums.length, min = 0;
        while (lo <= hi) {
            int mid = (lo + hi) >> 1;
            if (windowExist(mid, nums, s)) {
                hi = mid - 1;//找到就缩小窗口的大小
                min = mid;//如果找到就记录下来
            } else
                lo = mid + 1;//没找到就扩大窗口的大小
        }
        return min;
    }

    //size窗口的大小
    private boolean windowExist(int size, int[] nums, int s) {
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i >= size)
                sum -= nums[i - size];
            sum += nums[i];
            if (sum >= s)
                return true;
        }
        return false;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值