月度归档: 2021年7月

LeetCode刷题【84】 柱状图中最⼤的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

 

示例 1:

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

示例 2:

输入: heights = [2,4]
输出: 4

 

提示:

  • 1 <= heights.length <=105
  • 0 <= heights[i] <= 104
Related Topics
  • 数组
  • 单调栈
  • \n
  • 👍 1445
  • 👎 0
  • 题解

    直接说官方题解的思路,已测试用例【2,1,5,6,2,3】来说,在左侧-1,和6的索引上各新增一个虚拟节点,值为0。

    从左开始遍历,我们可以先在栈中压入一个-1作为打底。此处用Deque代替Stack使用,根据网友的说法Java 因为历史的原因 Stack 这个类设计得不好,官方建议使用Deque,只使用特定的方法从队位插入,从队尾弹出来模拟实现。具体可能需要看下Stack的底层源码实现。

    0)回归正题,当索引走到0的时候,对应的值2,对比栈顶当前索引-1对应的值0,2>0,那么我们直接把索引0压入栈。此时栈中情况为-1,0

    1)当索引到1的时候对应的值1,对比栈顶索引0,对应的值2,1<2,将2弹出,找到2高度为当前的2的时候能取得的最大面积的宽度,为当前的的索引1到当前栈顶0取出后的栈顶索引0之间的距离,为1。所以2对应的面积为2×1 = 2,对应索引0。

    在索引0弹出后,将索引1压入栈,当前栈中的情况为-1,1

    2)继续遍历下一个当前索引2,对应的值为5,对比栈顶索引1对应的值1,5大于1,所以直接压入栈。

    3)继续遍历下一个当前索引3,对应的值为6,对比当前栈顶索引2对应的值5,6大于5,直接压入栈。

    4)继续遍历下一个,当前索引4,对应的值2,2比6小,弹出栈顶索引3,并得到对应宽度为栈顶索引3弹出后的栈顶索引位置2,那么宽度就是4到2之间的距离个数4-2-1为1个,于是得到了当前位置的6对应的最大面积6×1=6。

    到目前位置已经知道的索引0位置的2对应的面积2,索引3位置的6对应的面积6。那么当前的最大面积就是6了。

    在索引3位置的6弹出后,当前索引4位置的2依旧还要和当前栈顶索引2的5对比。而5大于2,继续做相同的处理,将索引2弹出,栈中索引2的前一个索引是1,所以2位置的5对应的面积为5x(4-1-1) = 10。此时最大值面积为10。

    直到栈顶索引1对应的值1小于当前索引4对应的值2,将当前索引4压入栈

    5)下一个循环,当前索引5,对应的值3,比当前栈顶索引4对应的值2大,直接压入栈

    6)再下一个循环,数组部分的值已经结束,当前索引6,对应的值为我们之前虚拟的值0,而0小于3。弹出栈顶索引5,索引5对应的值为3,栈中索引5的前一个是索引4,那么可以知道索引5的高度3对应能取到的最大宽度为6-4-1 = 1,对应面积为3。当前最大面积依旧是10。

    在栈顶索引5弹出后,当前栈顶索引是4,对应的值2也大于0,继续弹出栈顶索引4,对应值2。同样的,栈顶索引4的前一个是1,当前位置的2能取得的最大宽度是6-1-1 = 4,最大面积为2×4 =8。最大面积还是10。

    索引4弹出后,继续往前对比索引1,1大于0,同样的操作。

    最终结束。

    下面是代码

    
    import java.util.ArrayDeque;
    import java.util.Deque;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
    
        private int max = 0;
        private int[] arr;
        public int largestRectangleArea(int[] heights) {
            arr = heights;
            Deque<Integer> deque = new ArrayDeque<>();
            deque.offer(-1);
            for (int i = 0; i <= arr.length; i++) {
                int num = getNumByIndex(i);
                int dequeLastIndex = deque.peekLast();
                int dequeLastNum = getNumByIndex(dequeLastIndex);
                if (num >= dequeLastNum){
                    //这边deque.offer(i)和下面else里的deque.offer(i)其实可以合并,
                    // 将if判断条件取反执行else里的逻辑,并最终都if外deque.offer(i)
                    deque.offer(i);
                }else {
                    while (!deque.isEmpty() && getNumByIndex(deque.peekLast())>num){
                        dequeLastIndex = deque.pollLast();
                        //最终终止边界不好判断的话,可以直接在这里判断-1索引终止
                        if (dequeLastIndex == -1){
                            return max;
                        }
                        dequeLastNum = getNumByIndex(dequeLastIndex);
                        int length = i - deque.peekLast() - 1;
                        int area = dequeLastNum * length;
                        max = Math.max(area,max);
                    }
                    deque.offer(i);
                }
            }
            return max;
        }
    
        private int getNumByIndex(int index){
            if (index==arr.length || index==-1){
                return 0;
            }
            return arr[index];
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    Java 字符串String补0方法 / ID编号生成器字符串补0

    这期工作有个需求,写个公文编号生成服务,提供公文编号生成的功能,给其他服务调用。

    要求,10位字符串,前2位是英文字母,后8位是数字。原来系统里也是有编号生成器的,但是实现逻辑不大符合要求,数字位部分包含了年月日信息,以及递增数字部分。也不是说这样的方式不行,但是比较浪费,不过编号中也包含了部分业务、时间信息。

    而对接的系统表示,他们的号码可用范围空间已经不多了,想要继续维持可持续的使用需要扩展编号字段长度,但是他们现在不会去改扩展这个长度,当前这个是一个老系统,需要修改的地方涉及很多,有风险。且他们已经在开发新系统了,预期需要半年左右可以接入,当前只能给我们分配一个新的前2位英文字母的区段给我们的3个系统一起使用。

    好吧,为了不浪费,且为了照顾后期编号长度拓展的需求于是有了这个公文编号生成服务的开发需求。实现起来也很简单,一般通用常规作法,需要有一个有原子性,且稳定递增的计数器,这个时候就有两个选择DB主键自增,或者Redis.inc方法来实现最终返回一个Long型数值。这也没啥纠结的直接redis了。

    接下来就是数字转字符串,并且需要前面补0到指定长度,直接上代码了

    public  static String charArrFunc(int num, int numLength, String prefix){
            String numStr = String.valueOf(num);
            char[] chars = numStr.toCharArray();
            char[] defaultArr = new char[numLength+prefix.length()];
            Arrays.fill(defaultArr,'0');
            for (int i = 0; i < prefix.length(); i++) {
                defaultArr[i] = prefix.charAt(i);
            }
            int dif = numLength - chars.length + prefix.length();
            System.arraycopy(chars, 0, defaultArr, dif, chars.length);
            return new String(defaultArr);
        }

    思路也很简单,生成指定长度的char[],全部填上0,在需要的位置填上需要的值,最后new String。写完之后自测了下,没毛病。但是我想了下,是不是还有其他方法,包括项目代码里原来也有类似的编码补0的现成方法,原来现成的方法是这样的

     public static String autoCompleteCode(String code, int num) {
            if (StringUtils.isEmpty(code)) {
                return "";
            }
            // 0代表前面补充0 d代表参数为正整数型
            return String.format("%0" + num + "d", Long.parseLong(code));
        }

    好像很简单,调用String.format,按照指定的格式来转换,到源码里看下是调用

    new Formatter().format(format, args).toString()

    来实现的,而再往下看也是很明显的使用了Pattern,性能必然不能相比。然后又搜了下,网上一般还有一种方法

    public static String TEMPLATE_ZERO = "00000000";
        public static String startZero(int num, String prefix){
            DecimalFormat g1 = new DecimalFormat(TEMPLATE_ZERO);
            return prefix+g1.format(num);
        }

    使用DecimalFormat来实现,基本网上看别人写的大多数都是上面这两种,可能稍微有一点变化。不过性能和自己写了操作char[]来比应该还是不能比的。但是从易用性,便捷开发的角度来讲,这两种方法是十分方便使用的。

    Continue reading

    LeetCode刷题【1800】 最大升序子数组和

    给你一个正整数组成的数组 nums ,返回 nums 中一个 升序 子数组的最大可能元素和。

    子数组是数组中的一个连续数字序列。

    已知子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,若对所有 il <= i < r),numsi < numsi+1 都成立,则称这一子数组为 升序 子数组。注意,大小为 1 的子数组也视作 升序 子数组。

     

    示例 1:

    输入:nums = [10,20,30,5,10,50]
    输出:65
    解释:[5,10,50] 是元素和最大的升序子数组,最大元素和为 65 。
    

    示例 2:

    输入:nums = [10,20,30,40,50]
    输出:150
    解释:[10,20,30,40,50] 是元素和最大的升序子数组,最大元素和为 150 。 
    

    示例 3:

    输入:nums = [12,17,15,13,10,11,12]
    输出:33
    解释:[10,11,12] 是元素和最大的升序子数组,最大元素和为 33 。 
    

    示例 4:

    输入:nums = [100,10,1]
    输出:100
    

     

    提示:

    • 1 <= nums.length <= 100
    • 1 <= nums[i] <= 100
    Related Topics
  • 数组
  • \n
  • 👍 16
  • 👎 0
  • 题解

    简单题,看人家发出来就顺手撸一下看看,没啥特别细节要点和技巧,单向遍历,根据递增特性判断下,是就继续加,不是则从头开始加。然后再遍历的过程中比较下已经得到过的最大值。

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int maxAscendingSum(int[] nums) {
            int max = nums[0];
            int tmp = nums[0];
            for (int i = 1; i < nums.length; i++) {
                if (nums[i]> nums[i-1]){
                    tmp += nums[i];
                }else{
                    tmp = nums[i];
                }
                max = Math.max(max,tmp);
            }
            return max;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【862】 和至少为 K 的最短子数组

    返回 A 的最短的非空连续子数组的长度,该子数组的和至少为 K

    如果没有和至少为 K 的非空子数组,返回 -1 。

     

    示例 1:

    输入:A = [1], K = 1
    输出:1
    

    示例 2:

    输入:A = [1,2], K = 4
    输出:-1
    

    示例 3:

    输入:A = [2,-1,2], K = 3
    输出:3
    

     

    提示:

    1. 1 <= A.length <= 50000
    2. -10 ^ 5 <= A[i] <= 10 ^ 5
    3. 1 <= K <= 10 ^ 9
    Related Topics
  • 队列
  • 数组
  • 二分查找
  • 前缀和
  • 滑动窗口
  • 单调队列
  • 堆(优先队列)
  • \n
  • 👍 284
  • 👎 0
  • 题解

    class Solution {
        public int shortestSubarray(int[] nums, int k) {
            int[] sum = new int[nums.length+1];
            for (int i = 0; i < nums.length; i++) {
                if (k <= nums[i]){
                    return 1;
                }
                sum[i+1] = nums[i] + sum[i];
            }
            //前缀和数组sum
            int res = Integer.MAX_VALUE;
            Deque<Integer> queue = new LinkedList<>();
            for(int i=0;i<sum.length;i++){
                while(!queue.isEmpty()&&sum[i]<=sum[queue.peekLast()]) {
                    queue.pollLast();
                }
                while(!queue.isEmpty()&&sum[i]-sum[queue.peek()]>=k) {
                    res = Math.min(res,i-queue.poll());
                }
                queue.add(i);
            }
            return res==Integer.MAX_VALUE?-1:res;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题 【剑指 Offer 64】队列的最大值

    1+2+...+n ,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

     

    示例 1:

    输入: n = 3
    输出: 6
    

    示例 2:

    输入: n = 9
    输出: 45
    

     

    限制:

    • 1 <= n <= 10000
    Related Topics
  • 位运算
  • 递归
  • 脑筋急转弯
  • \n
  • 👍 347
  • 👎 0
  • 题解

    &&短路符号的特性,

    或者的话试试位运算的方法,二进制的相关东西太久远,忘了好多,暂时 不深入了

    主要思路还是用&&控制递归深度,因为这里不能用if,二元运算等方法来判断了

    联动下前面的斐波那契数列的计算,加上了这些限制条件之后,也让解法变得有趣很多

    在递归外定义一个变量,用控制递归次数的方法,依次为这个变量加运算

    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        int d = 0;
    
        public int sumNums(int n) {
            dep(n);
            return d;
        }
    
        public int dep(int n){
            d = d + n;
            boolean b = n>0 && dep(n-1)>0;
            return n;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    直接递归:

        //另外一个方法
        public int sumNums(int n) {
            boolean t = (n>0) && (n = n + sumNums(n-1))>0;
            return n;
        }

    LeetCode刷题【365】水壶问题

    有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?

    如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。

    你允许:

    • 装满任意一个水壶
    • 清空任意一个水壶
    • 从一个水壶向另外一个水壶倒水,直到装满或者倒空

    示例 1: (From the famous "Die Hard" example)

    输入: x = 3, y = 5, z = 4
    输出: True
    

    示例 2:

    输入: x = 2, y = 6, z = 5
    输出: False
    
    Related Topics
  • 深度优先搜索
  • 广度优先搜索
  • 数学
  • \n
  • 👍 301
  • 👎 0
  • 题解

    首先想到的是深搜,代码写完了开心提交,emmm,问题来了。有个用例过不去,提示超时,这个用例是

    104579, 104593, 12444

    1水壶容量104579,2水壶容量104593,目标水量12444

    自己在本地写了个test试了下,,提示栈溢出,修改-Xss参数多次尝试之后终于能跑过去了,这个时候的设置是-Xss102400k,加了个标记看了栈深度大概看了下,好家伙,直接跑到了362138。这只是个大概值,并不肯定十分准确。

    先贴下这个代码,思路应该还是比较明晰的。

    
    import org.junit.Test;
    
    import java.util.HashSet;
    
    /**
     * LeetCode365<br>
     * [在此填写类描述]
     *
     * @date 2021/7/14 19:54
     * @see [相关类/方法](可选)
     * @since [产品/模块版本] (可选)
     */
    public class LeetCode365 {
    
        @Test
        public void test(){
            System.out.println(canMeasureWater(104579, 104593, 12444));;
        }
    
    
        private HashSet<String> visited = new HashSet<>();
    
        private boolean ifCan = false;
    
        private  int target;
    
        private int jug1Max;
    
        private int jug2Max;
    
        private int depth = 0;
    
        public boolean canMeasureWater(int jug1Capacity, int jug2Capacity, int targetCapacity) {
            this.target = targetCapacity;
            this.jug1Max = jug1Capacity;
            this.jug2Max = jug2Capacity;
            if (jug1Max+jug2Max < target){
                return false;
            }
            tryNext( 0 ,0 ,1);
            System.out.println(depth);
            return ifCan;
        }
    
    
        private void tryNext( int jug1Current, int jug2Current, int count){
            count++;
            if (count > this.depth){
    
                this.depth = count;
            }
            if (ifCan)return;
            if (jug1Current == target || jug2Current == target || jug1Current + jug2Current == target){
                ifCan = true;
                return;
            }
            if (visited.contains(jug1Current+"_"+jug2Current)){
                return;
            }
            visited.add(jug1Current+"_"+jug2Current);
            //下一步几种情况
            //往1瓶子倒满水, 但是只有在1瓶子水没满的情况下可以
            if (jug1Current < jug1Max){
                tryNext(jug1Max,jug2Current,count);
            }
            //往2瓶子倒满水,但是只有在2瓶子水没有满的情况下可以
            if (jug2Current < jug2Max){
                tryNext(jug1Current,jug2Max,count);
            }
            if ( jug1Current > 0 && jug2Current>0){
                //把1瓶子的水倒掉,但是只在1瓶子里有水的情况可以,且2瓶子里也有水,如果2瓶子没水会回到0_0的状态
                tryNext(0,jug2Current,count);
                //把2瓶子的水倒掉,但是只在2瓶子里有水的情况可以,且1瓶子里也有水,如果1瓶子没水会回到0_0的状态
                tryNext(jug1Current,0,count);
            }
            //把一个瓶子里的水倒到另外一个瓶子里分情况
            //1.倒到满,倒出的瓶子还有剩余
            //2.倒完了,被倒入的瓶子还没满
            //中间正好满的情况都可以并在这两个里面,但是因为前面其实有两个瓶子各倒满一个的情况了,其实可以忽略
    
            //把1瓶子的水倒入2瓶子,余下的水留在1瓶子中,但是只有在2瓶子水没满,且1瓶子有水的情况下
            if (jug2Current < jug2Max && jug1Current > 0){
                if ( jug1Current + jug2Current >= jug2Max){
                    //如果倒满了还有剩余
                    tryNext(jug1Current-(jug2Max-jug2Current),jug2Max,count);
                }else {
                    //如果能全倒进去
                    tryNext(0,jug1Current+jug2Current,count);
                }
            }
            //把2瓶子的水倒入1瓶子,余下的水留在2瓶子中,但是只有在1瓶子水没满,且2瓶子有水的情况下
            if (jug1Current < jug1Max && jug2Current > 0){
                if ( jug1Current + jug2Current >= jug1Max){
                    //如果倒满了还有剩余
                    tryNext(jug1Max,jug2Current - (jug1Max-jug1Current),count);
                }else {
                    //如果能全倒进去
                    tryNext(jug1Current + jug2Current,0,count);
                }
            }
        }
    }

    既然好吧,换个思路看看了。把深度递归调用改成队列试试呢。

    
        private HashSet<String> visited = new HashSet<>();
    
        private  int target;
    
        private int jug1Max;
    
        private int jug2Max;
    
        private Queue<int[]> queue = new LinkedList<>();
    
        public boolean canMeasureWater(int jug1Capacity, int jug2Capacity, int targetCapacity) {
            this.target = targetCapacity;
            this.jug1Max = jug1Capacity;
            this.jug2Max = jug2Capacity;
            if (jug1Max+jug2Max < target){
                return false;
            }
            queue.add(new int[]{0, 0});
            while (!queue.isEmpty()){
                int[] arr = queue.poll();
                int jug1Current = arr[0];
                int jug2Current = arr[1];
                //1和2的当前状态已经拿到了,那么和之前以前判断当前的状态并得到下一个可能的状态
                //将下一个可能的状态放到队列尾部
                if (jug1Current == target || jug2Current == target || jug1Current + jug2Current == target){
                    return true;
                }
                visited.add(jug1Current+"_"+jug2Current);
                if (jug1Current < jug1Max){
                    addNext(jug1Max,jug2Current);
                }
                //往2瓶子倒满水,但是只有在2瓶子水没有满的情况下可以
                if (jug2Current < jug2Max){
                    addNext(jug1Current,jug2Max);
                }
                if ( jug1Current > 0 && jug2Current>0){
                    //把1瓶子的水倒掉,但是只在1瓶子里有水的情况可以,且2瓶子里也有水,如果2瓶子没水会回到0_0的状态
                    addNext(0,jug2Current);
                    //把2瓶子的水倒掉,但是只在2瓶子里有水的情况可以,且1瓶子里也有水,如果1瓶子没水会回到0_0的状态
                    addNext(jug1Current,0);
                }
                //把一个瓶子里的水倒到另外一个瓶子里分情况
                //1.倒到满,倒出的瓶子还有剩余
                //2.倒完了,被倒入的瓶子还没满
                //中间正好满的情况都可以并在这两个里面,但是因为前面其实有两个瓶子各倒满一个的情况了,其实可以忽略
    
                //把1瓶子的水倒入2瓶子,余下的水留在1瓶子中,但是只有在2瓶子水没满,且1瓶子有水的情况下
                if (jug2Current < jug2Max && jug1Current > 0){
                    if ( jug1Current + jug2Current >= jug2Max){
                        //如果倒满了还有剩余
                        addNext(jug1Current-(jug2Max-jug2Current),jug2Max);
                    }else {
                        //如果能全倒进去
                        addNext(0,jug1Current+jug2Current);
                    }
                }
                //把2瓶子的水倒入1瓶子,余下的水留在2瓶子中,但是只有在1瓶子水没满,且2瓶子有水的情况下
                if (jug1Current < jug1Max && jug2Current > 0){
                    if ( jug1Current + jug2Current >= jug1Max){
                        //如果倒满了还有剩余
                        addNext(jug1Max,jug2Current - (jug1Max-jug1Current));
                    }else {
                        //如果能全倒进去
                        addNext(jug1Current + jug2Current,0);
                    }
                }
            }
            return false;
        }
        
        private void addNext(int jug1Current,int jug2Current){
            if (visited.contains(jug1Current+"_"+jug2Current)){
                //已经有过,不添加
                return;
            }
            queue.add(new int[]{jug1Current,jug2Current});
        }

    写queue版本的时候原本把已访问的判断写在poll之后的,但是想了下,还是放在了添加进queue之前,这样也许可以减少点判断。

    if (visited.contains(jug1Current+"_"+jug2Current))

    如果是写在poll之后的话,就用CONTINUE中断,继续处理queue中的下一个。

    另外,写queue这个版本的时候,可以看到,大部分逻辑代码和之前的递归深搜的是基本一致的,区别仅仅在于,递归是调用自身,而queue这个是把下一个可能的状态加进队列,等待处理。是不是看到的BFS的影子,确实,本质上来说这两种方法就是DFS和BFS的区别。

    LeetCode刷题【43】字符串相乘

    给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

    示例 1:

    输入: num1 = "2", num2 = "3"
    输出: "6"

    示例 2:

    输入: num1 = "123", num2 = "456"
    输出: "56088"

    说明:

    1. num1 和 num2 的长度小于110。
    2. num1 和 num2 只包含数字 0-9
    3. num1 和 num2 均不以零开头,除非是数字 0 本身。
    4. 不能使用任何标准库的大数类型(比如 BigInteger)直接将输入转换为整数来处理
    Related Topics
  • 数学
  • 字符串
  • 模拟
  • \n
  • 👍 670
  • 👎 0
  • 题解

    按照基本的乘法计算规则,分别计算每一位即可

    
    
    import java.util.Arrays;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public String multiply(String num1, String num2) {
            if ("0".equals(num1)||"0".equals(num2)){
                return "0";
            }
            if ("1".equals(num1)){
                return num2;
            }
            if ("1".equals(num2)){
                return num1;
            }
            int[] res = new int[num1.length() + num2.length()];
            Arrays.fill(res,0);
            for (int k1 = num1.length()-1; k1 >= 0 ; k1--) {
                int n1 = num1.charAt(k1) - '0';
                for (int k2 = num2.length()-1; k2 >=0 ; k2--) {
                    int n2 = num2.charAt(k2) - '0';
                    //开始往res上堆结果
                    int k = k1 + k2 + 1;
                    int tmp = res[k] + (n2 * n1);
                    if (tmp >= 10){
                        res[k] = tmp % 10;
                        res[k-1] = res[k-1] + tmp/10;
                    }else{
                        res[k] = tmp;
                    }
                }
            }
            StringBuffer stringBuffer = new StringBuffer();
            for (int i = 0; i < res.length; i++) {
                if (i==0 && res[i] == 0){
                    continue;
                }
                stringBuffer.append(res[i]);
            }
            return stringBuffer.toString();
    
    
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    

    补个小知识点Char转int

            System.out.println((int)("123".toCharArray()[0]));  //49
            System.out.println(("123".toCharArray()[0]-'0'));  //1
            System.out.println(("123".toCharArray()[0]+'0'));  //97
            System.out.println(('1'+'0'));  //97
            System.out.println((Character.getNumericValue("123".toCharArray()[0])));//1