• 单调栈-42. 接雨水


    单调栈-42. 接雨水

    1.题目

    给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

    在这里插入图片描述

    示例 1:

    输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    输出:6
    解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 
    
    示例 2:
    
    输入:height = [4,2,0,3,2,5]
    输出:9
    
    来源:力扣(LeetCode)
    链接:https://leetcode.cn/problems/trapping-rain-water
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.解题思路

    共有是三种方法解决:

    方法一:双指针法

    请添加图片描述

    然后,当前列的雨水高度等于,min(LH,RH)-当前列高度。

    特殊情况:第一列和最后以列不操作。

    方法二:动态规划

    其实是对双指针法的一种优化,因为在使用双指针法的时候,发现,在求每列的左右最大值的时候,存在着重复的计算,如果利用动态规划的方法记录。

    即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);

    从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);

    方法三:单调栈

    请添加图片描述

    分为三种情况:

    (1)如果当前遍历的元素(柱子)高度小于栈顶元素的高度,就把这个元素加入栈中,因为栈里本来就要保持从小到大的顺序(从栈头到栈底)。

    (2)如果当前遍历的元素(柱子)高度等于栈顶元素的高度,要跟更新栈顶元素,因为遇到相相同高度的柱子,需要使用最右边的柱子来计算宽度。

    (3)如果当前遍历的元素(柱子)高度大于栈顶元素的高度,此时就出现凹槽了(这种情况会出现雨水)

    其实质就是:栈顶和栈顶的下一个元素以及要入栈的三个元素来接水!

    请添加图片描述

    凹槽的高度:int h = min(height[st.top()], height[i]) - height[mid];

    凹槽的宽度:int w = i - st.top() - 1 ;

    凹槽的体积:h*w

    3.代码

    方法一:双指针法(非最优)

    public int trap(int[] height) {
            int sum = 0;
            for (int i = 0; i < height.length; i++) {
                // 第一个柱子和最后一个柱子不接雨水
                if (i==0 || i== height.length - 1) continue;
                
                int rHeight = height[i]; // 记录右边柱子的最高高度
                int lHeight = height[i]; // 记录左边柱子的最高高度
                for (int r = i+1; r < height.length; r++) {
                    if (height[r] > rHeight) rHeight = height[r];
                }
                for (int l = i-1; l >= 0; l--) {
                    if(height[l] > lHeight) lHeight = height[l];
                }
                int h = Math.min(lHeight, rHeight) - height[i];
                if (h > 0) sum += h;
            }
            return sum;
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    方法二:动态规划

    public int trap(int[] height) {
            int length = height.length;
            if (length <= 2) return 0;
            int[] maxLeft = new int[length];
            int[] maxRight = new int[length];
            
            // 记录每个柱子左边柱子最大高度
            maxLeft[0] = height[0];
            for (int i = 1; i< length; i++) maxLeft[i] = Math.max(height[i], maxLeft[i-1]);
            
            // 记录每个柱子右边柱子最大高度
            maxRight[length - 1] = height[length - 1];
            for(int i = length - 2; i >= 0; i--) maxRight[i] = Math.max(height[i], maxRight[i+1]);
            
            // 求和
            int sum = 0;
            for (int i = 0; i < length; i++) {
                int count = Math.min(maxLeft[i], maxRight[i]) - height[i];
                if (count > 0) sum += count;
            }
            return sum;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    方法三:单调栈法

    public int trap(int[] height){
            int size = height.length;
    
            if (size <= 2) return 0;
            
            // in the stack, we push the index of array
            // using height[] to access the real height
            Stack<Integer> stack = new Stack<Integer>();
            stack.push(0);
    
            int sum = 0;
            for (int index = 1; index < size; index++){
                int stackTop = stack.peek();
                if (height[index] < height[stackTop]){
                    stack.push(index);
                }else if (height[index] == height[stackTop]){
                    // 因为相等的相邻墙,左边一个是不可能存放雨水的,所以pop左边的index, push当前的index
                    stack.pop();
                    stack.push(index);
                }else{
                    //pop up all lower value
                    int heightAtIdx = height[index];
                    while (!stack.isEmpty() && (heightAtIdx > height[stackTop])){
                        int mid = stack.pop();
                        
                        if (!stack.isEmpty()){
                            int left = stack.peek();
    
                            int h = Math.min(height[left], height[index]) - height[mid];
                            int w = index - left - 1;
                            int hold = h * w;
                            if (hold > 0) sum += hold;
                            stackTop = stack.peek();
                        }
                    }
                    stack.push(index);
                }
            }
            
            return sum;
        }
    
    • 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
    • 38
    • 39
    • 40
    • 41

    4.性能

    方法一:双指针(非最优)

    • 时间复杂度:O(n^2)
    • 空间复杂度:O(l)

    方法二:动态规划

    • 时间复杂度:O(n)
    • 空间复杂度:O(n)

    方法三:单调栈

    • 时间复杂度:O(n)
    • 空间复杂度:O(n)
  • 相关阅读:
    R语言使用mlr包创建回归任务、指定回归学习函数为随机森林回归模型、网格搜索、交叉验证获取随机森林的最佳超参数组合、结合最优参数组合训练最终的随机森林回归模型
    企典软件:一套系统解决85%以上企业管理难题
    Vuex详解(五种状态)
    【Spring Boot】RabbitMQ消息队列 — RabbitMQ入门
    机械臂示教轨迹参数化方法 DMP, Dynamic Movement Primitive (一)
    springboot中使用redis管理session
    2000-2022年各区县农产品产量数据
    持续集成部署 - Jenkinsfile中单双引号的区别
    docker环境部署ruoyi系统前后端分离项目
    关于CPP类中字符串成员初始化
  • 原文地址:https://blog.csdn.net/qq_42569028/article/details/125566544