algorithm-base/animation-simulation/单调队列单调栈/接雨水.md
2021-07-28 02:26:32 +08:00

8.5 KiB
Raw Blame History

如果阅读时,发现错误,或者动画不可以显示的问题可以添加我微信好友 tan45du_one ,备注 github + 题目 + 问题 向我反馈

感谢支持,该仓库会一直维护,希望对各位有一丢丢帮助。

另外希望手机阅读的同学可以来我的 公众号:袁厨的算法小屋 两个平台同步,想要和题友一起刷题,互相监督的同学,可以在我的小屋点击刷题小队进入。

42. 接雨水

这道接雨水也是一道特别经典的题目,一道必刷题目,我们也用单调栈来解决。下面我们来看一下题目吧

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

示例 1

输入height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出6

示例 2

输入height = [4,2,0,3,2,5]
输出9

示例 3

输入:[4,3,2,0,1,1,5]
输出13

上面是由数组 [4,3,2,0,1,1,5]表示的高度图,在这种情况下,可以接 13 个单位的雨水(见下图)。

题目解析:

看了上面的示例刚开始刷题的同学可能有些懵逼,那我们结合图片来理解一下,我们就用示例 3 的例子进行举例,他的雨水到底代表的是什么。

上图则为我们的题目描述,是不是理解了呢?你也可以这样理解我们在地上放置了若干高度的黄色箱子,他们中间有空隙,然后我们想在他们里面插入若干蓝色箱子,并保证插入之后,这些箱子的左视图和右视图都不能看到蓝色箱子。

好啦题目我们已经理解了,下面我们看一下解题思路。做这个这前我们可以先去看一下我们之前做过的另一道题目每日温度。这两道题目的思路差不多,都是利用了单调栈的思想,下面我们来看一下具体思路吧。

这里我们也系统的说一下单调栈,单调栈含义就是栈内的元素是单调的,我们这两个题目用到的都是递减栈(相同也可以),我们依次将元素压入栈,如果当前元素小于等于栈顶元素则入栈,如果大于栈顶元素则先将栈顶不断出栈,直到当前元素小于或等于栈顶元素为止,然后再将当前元素入栈。就比如下图的 4想入栈的话则需要 23 出栈之后才能入栈,因为 4 大于他俩。

在这里插入图片描述

我们了解单调栈的含义下面我们来看一下接雨水问题到底该怎么做,其实原理也很简单,我们通过我们的例 3 来进行说明。

首先我们依次入栈 4320 我们的数组前四个元素是符合单调栈规则的。但是我们的第五个 1是大于 0 的。那我们就需要 0 出栈 1 入栈。但是我们这样做是为了什么呢?有什么意义呢?别急我们来看下图。

在这里插入图片描述

上图我们的4320 已经入栈了,我们的另一个元素为 1栈顶元素为 0栈顶下的元素为 2。那么我们在这一层接到的雨水数量怎么算呢201 这三个元素可以接住的水为一个单位(见下图)这是我们第一层接到水的数量。

注:能接到水的情况,肯定是中间低两边高,这样才可以。

在这里插入图片描述

因为我们需要维护一个单调栈,所以我们则需要将 0 出栈 1 入栈,那么此时栈内元素为 4321。下一位元素为 1我们入栈此时栈内元素为 43211。下一元素为 5栈顶元素为 1栈顶的下一元素仍为 1则需要再下一个元素为 2那我们求当前层接到的水的数量。

在这里插入图片描述

我们是通过 2115 这四个元素求得第二层的接水数为 1*3=3;1 是因为 min(2-1,5-1)=min(1,4)得来的,大家可以思考一下木桶效应。装水的多少,肯定是按最短的那个木板来的,所以高度为 13 的话是因为 5 的索引为 62 的索引为 2他们之间共有三个元素345也就是 3 个单位。所以为 6-2-1=3。

将 1 出栈之后,我们栈顶元素就变成了 2下一元素变成了 3那么 325 这三个元素同样也可以接到水。

在这里插入图片描述

这是第三层的接水情况,能够接到 4 个单位的水,下面我们继续出栈 2那么我们的 435 仍然可以接到水啊。

在这里插入图片描述

这是我们第四层接水的情况,一共能够接到 5 个单位的水,那么我们总的接水数加起来,那就是

1+3+4+5=13。你学会了吗别急还有视频我们我们再来深入理解一哈。

题目代码:

class Solution {
    public int trap(int[] height) {
         Stack<Integer> stack = new Stack<Integer>();
         int water = 0;
         //特殊情况
         if(height.length <3){
             return 0;
         }
         for(int i = 0; i < height.length; i++){
             while(!stack.isEmpty() && height[i] > height[stack.peek()]){
                 //栈顶元素
                 int popnum = stack.pop();
                 //相同元素的情况例11
                 while(!stack.isEmpty()&&height[popnum] == height[stack.peek()]){
                     stack.pop();
                 }
                 //计算该层的水的单位
                 if(!stack.isEmpty()){
                     int temp = height[stack.peek()];//栈顶元素值
                     //高
                     int hig = Math.min(temp-height[popnum],height[i]-height[popnum]);
                     //宽
                     int wid = i-stack.peek()-1;
                     water +=hig * wid;
                 }

             }
             //这里入栈的是索引
             stack.push(i);
         }
         return water;
    }
}

GO Code:

func trap(height []int) int {
    stack := []int{}
    water := 0
    // 最左边部分不会接雨水左边持续升高时stack都会弹出所有元素。
    for i := 0; i< len(height); i++ {
        for len(stack) != 0 && height[i] > height[stack[len(stack) - 1]] {
            popnum := stack[len(stack) - 1]
            // 出现相同高度的情况其实也可以不用处理如果不处理相同高度时后面的hig为0会产生很多无效的计算
            for len(stack) != 0 && height[popnum] == height[stack[len(stack) - 1]] {
                stack = stack[:len(stack) - 1]
            }
            if len(stack) == 0 { break }
            le, ri := stack[len(stack) - 1], i
            hig := min(height[ri], height[le]) - height[popnum]
            wid := ri - le - 1
            water += wid * hig
        }
        stack = append(stack, i)
    }
    return water
}

func min(a, b int) int {
    if a < b { return a }
    return b
}