• 【LeetCode每日一题】【单调队列】2022-10-26 862. 和至少为K的最短子数组 Java实现



    题目链接

    https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/

    题目

    在这里插入图片描述

    思路

    https://leetcode.cn/problems/shortest-subarray-with-sum-at-least-k/solution/liang-zhang-tu-miao-dong-dan-diao-dui-li-9fvh/

    前缀和

    定义前缀和 s [ 0 ] = 0 s[0]=0 s[0]=0 s [ i + 1 ] = ∑ j = 0 i n u m s [ j ] s[i+1]=\sum\limits_{j=0}^{i}nums[j] s[i+1]=j=0inums[j]

    例如,nums = [1,2,-1,2],对应的前缀和数组为s=[0,1,3,2,4]

    通过前缀和,我们可以把子数组的和转换成两个前缀和的差,即

    ∑ j = l e f t r i g h t n u m s [ j ] = ∑ j = 0 r i g h t n u m s [ j ] − ∑ j = 0 l e f t n u m s [ j ] = s [ r i g h t + 1 ] − s [ l e f t ] \sum\limits^{right}_{j=left}nums[j]=\sum\limits^{right}_{j=0}nums[j]-\sum\limits^{left}_{j=0}nums[j]=s[right+1]-s[left] j=leftrightnums[j]=j=0rightnums[j]j=0leftnums[j]=s[right+1]s[left]

    例如,nums的子数组[2,-1,2]的和就可以用s[4]-s[1]=4-1=3,这时,right=3,left=1

    注意:
    为了方便计算,常用左闭右开区间[left,right)来表示子数组,此时子数组的和为s[right]-s[left],子数组的长度为right-left

    暴力法

    求出nums的前缀和s后,可以用暴力法,枚举所有的i>j且s[i]-s[j]>=k的子数组[j,i),取其中最小的i-j作为答案

    在这里插入图片描述

    import java.util.Arrays;
    
    class Solution {
        public int shortestSubarray(int[] nums, int k) {
            int[] prefixSum = new int[nums.length + 1];
            prefixSum[0] = 0;
            for (int i = 1; i < prefixSum.length; i++) {
                prefixSum[i] = prefixSum[i - 1] + nums[i - 1];
            }
            int arrLen = Integer.MAX_VALUE;
            for (int i = 1; i < prefixSum.length; i++) {
                for (int j = 0; j < i; j++) {
                    if (prefixSum[i] - prefixSum[j] >= k) {
                        arrLen = Math.min(arrLen, i - j);
                    }
                }
            }
            return arrLen == Integer.MAX_VALUE ? -1 : arrLen;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    暴力法的时间复杂度是 O ( n 2 ) O(n^2) O(n2)

    可以在遍历前缀和s数组的时候,同时用某个合适的数据结构来维护遍历过的s[i],并即时移除无用的s[i]

    优化一

    遍历到s[i]时,考虑左边的某个s[j],如果s[i]-s[j]>=k,那么无论s[i]右边的数字是大是小,都不可能把j当作子数组的左端点,得到一个比i-j更短的子数组。因此s[j]没有任何作用了

    优化二

    如果s[i]<=s[j],加入后续有数字x能和s[j]组成满足要求的子数组,即x-s[j]>=k,那么必然也有x-s[i]>=k,由于从s[i]到x这段数组更短,因此s[j]没有任何作用了

    做完这两个优化后,再把s[i]加到这个数据结构中

    由于优化二保证了数据结构中的s[i]会形成一个递增序列,因此优化一移除的是序列最左侧的若干元素,优化二移除的是序列最右侧的若干元素。我们需要一个数据结构,它支持移除最左端的元素和最右端的元素,以及在最右端添加元素,故选用双端队列

    由于双端队列的元素始终保持单调递增,因此这种数据结构也叫做单调队列

    另一种写法

    在计算前缀和的同时去计算答案,这需要在双端队列中额外存储前缀和的值

    由于前缀和的初始值0在遍历nums之前就算出来了,因此需要在遍历之前,往双端队列中插入前缀和0及其下标-1

    关于-1的解释
    因为上面遍历的是s,下面遍历的nums,两者的下标偏移了一位

    import java.util.LinkedList;
    
    class Solution {
        public int shortestSubarray(int[] nums, int k) {
    
            int res = Integer.MAX_VALUE;
            LinkedList<Pair> q = new LinkedList<>();
            q.add(new Pair(0L, -1));
            long curS = 0L;
            for (int i = 0; i < nums.length; i++) {
                curS += nums[i];
                //优化一
                while (!q.isEmpty() && curS - q.getFirst().key >= k) {
                    res = Math.min(res, i - q.pollFirst().value);
                }
                //优化二
                while (!q.isEmpty() && q.getLast().key >= curS) {
                    q.pollLast();
                }
                q.add(new Pair(curS, i));
            }
            return res == Integer.MAX_VALUE ? -1 : res;
        }
    
        class Pair {
            Long key;
            Integer value;
    
            public Pair() {
            }
    
            public Pair(Long key, Integer value) {
                this.key = key;
                this.value = value;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
  • 相关阅读:
    nprogress进度条的安装与使用
    LeetCode刷题---无重复字符的最长子串
    鸿鹄工程项目管理系统 Spring Cloud+Spring Boot+前后端分离构建工程项目管理系统
    算法的时间复杂度和空间复杂度
    当出现“无法成功完成操作,因为文件包含病毒或潜在的垃圾软件“时的解决办法
    Ai项目十四:基于 LeNet5 的手写数字识别及训练
    ASP.NET Core 8 的内存占用可以更低吗?
    融云「 IM 进阶实战高手课」系列直播上线
    虹科案例 | 空调故障无冷气,且没有故障码
    省去findViewById()方法,kotlin-android-extensions插件
  • 原文地址:https://blog.csdn.net/guliguliguliguli/article/details/127525669