• 209、长度最小的子数组


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

    方法一:暴力法

      暴力法是最直观的方法。初始化子数组的最小长度为无穷大,枚举数组nums中的每个下标作为子数组的开始下标对于每个开始下标 i,需要找到大于或等于 i 的最小下标 j,使得从nums[i] 到nums[j] 的元素和大于或等于 s并更新子数组的最小长度(此时子数组的长度是 j-i+1)。一直枚举到给定数组最后一个元素的小标作为子数组的开始下标。

    public int minSubArrayLen3(int s, int[] nums){
            int n = nums.length;
            //数组长度为0,则直接返回0
            if (n == 0) {
                return 0;
            }
            //初始化子数组长度为一个极大值
            int ans = 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 >= s) {
                    	//当累加和大于 s 时更新最小子数组长度
                        ans = Math.min(ans, j - i + 1);
                        break;
                    }
                }
            }
            //如果ans未改变则代表所有的累加和均小于s,则返回0
            return ans == Integer.MAX_VALUE ? 0 : ans;
        }
    

    时间复杂度:O(n^2),其中 nn 是数组的长度。需要遍历每个下标作为子数组的开始下标,对于每个开始下标,需要遍历其后面的下标得到长度最小的子数组。
    空间复杂度:O(1)


    方法二:双指针 *** 最优解 ***

      定义两个指针 start 和 end 分别表示子数组的开始位置和结束位置,维护变量 sum 存储子数组中的元素和(即从 nums[start] 到 nums[end] 的元素和)

    初始状态下,start 和 end 都指向下标 0,sum 的值为 0。

      每一轮迭代,将 nums[end] 加到 sum,如果 sum≥s,则更新子数组的最小长度(此时子数组的长度是 end−start+1),然后将 nums[start] 从sum 中减去并将 start 右移,直到 sum<s,在此过程中同样更新子数组的最小长度。在每一轮迭代的最后,将 end 右移。直到 end 大于等于数组的长度时为止。

    public int minSubArrayLen5(int s, int[] nums){
            int n = nums.length;
            if (n == 0) {
                return 0;
            }
            int ans = Integer.MAX_VALUE;
            int start = 0, end = 0;
            int sum = 0;
            while (end < n) {
                sum += nums[end];
                while (sum >= s) {
                    ans = Math.min(ans, end - start + 1);
                    sum -= nums[start];
                    start++;
                }
                end++;
            }
            return ans == Integer.MAX_VALUE ? 0 : ans;
        }
    

    时间复杂度:O(n),其中 n 是数组的长度。指针 start 和 end 最多各移动 n 次。
    空间复杂度:O(1)


    方法三:前缀+二分法查找

      方法一的时间复杂度是 O(n^2),因为在确定每个子数组的开始下标后,找到长度最小的子数组需要 O(n) 的时间。如果使用二分查找,则可以将时间优化到 O(logn)

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

      因为这道题保证了数组中每个元素都为正,所以前缀和一定是递增的,这一点保证了二分的正确性。如果题目没有说明数组中每个元素都为正,这里就不能使用二分来查找这个位置了。

    public int minSubArrayLen4(int s, int[] nums){
            int n = nums.length;
            if (n == 0) {
                return 0;
            }
            int ans = Integer.MAX_VALUE;
            int[] sums = new int[n + 1];
            // 为了方便计算,令 size = n + 1
            // sums[0] = 0 意味着前 0 个元素的前缀和为 0
            // sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
            // 以此类推
            for (int i = 1; i <= n; i++) {
                sums[i] = sums[i - 1] + nums[i - 1];
            }
            for (int i = 1; i <= n; i++) {
                //target加上前面的累加和,即相当于从当前i值开始求累加和看是否大于原始target值
                //依次求出每个i对应的下标开始连续子数组值累加和大于target的最小长度
                int target = s + sums[i - 1];
                int bound = Arrays.binarySearch(sums, target);
                if (bound < 0) {
                    bound = -bound - 1;
                }
                if (bound <= n) {
                    ans = Math.min(ans, bound - (i - 1));
                }
            }
            return ans == Integer.MAX_VALUE ? 0 : ans;
        }
    

    时间复杂度:O(nlogn),其中 n 是数组的长度。需要遍历每个下标作为子数组的开始下标,遍历的时间复杂度是O(n),对于每个开始下标,需要通过二分查找得到长度最小的子数组,二分查找得时间复杂度是 O(logn),因此总时间复杂度是 O(nlog n)
    空间复杂度:O(n),其中 nn 是数组的长度。额外创建数组sums 存储前缀和。

    参考:
    Leetcode官方题解

  • 相关阅读:
    sqlalchemy学习-- 重要参数
    sqlalchemy 学习-- 多表操作
    sqlalchemy 学习--单表操作
    python 获取命令行参数
    subline 相关
    semver 版本号命名法
    nginx四层负载nginx七层负载,nginx基于nginx-sticky会话保持.
    nginx知识点汇总
    Kubernetes操作图
    redis安装和基础使用
  • 原文地址:https://www.cnblogs.com/firecode7/p/16120434.html
Copyright © 2020-2023  润新知