# 接雨水

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

示例 1:

img

输入: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

提示:

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

# 思路分析:

  1. 我们可以维护一个单调栈,把柱子一个个的加入到单调栈中
  2. 当加入一个新元素时,如果发现需要弹出 元素,表示遇到了一个 凹陷的位置,此时应该计算雨水容量

比如说 一开始 加入了一个高度为 1 的柱子

image-20240108193022190

然后又加入了 高度 为 0 的柱子,此时是可以的因为不违反单调的规则

image-20240108193106530

接下来要加入一个高度为 2 的柱子了,此时 它 违反了单调规则,它要把之前比 2 小的柱子弹出栈

image-20240108193202832

此时弹出栈就意味着此时遇到了一个凹陷的位置了。此时我们就可以去计算雨水的容量了

这些柱子都有 left 和 right 高度,和 i ,j 宽度

image-20240108193446588

那么我们计算 雨水的容量就可以 通过 j - i - 1 先计算出 宽度,然后 取 最小柱子 。 然后 宽度 * 高度 就是当前凹陷区域的雨水容量了,以此类推我们将所以的雨水容量累加起来就是雨水的总容量了

下面通过流程分析,来理解 单调栈的解题流程

首先放入一个高度为 0 的柱子

image-20240108194208190

接下来要放入高度为 1 的柱子了,此时 就 违反了 单调栈的规则了,此时就需要将之前高度为 0 的柱子弹出来。前面说了 弹出柱子时我们要计算雨水的容量,但是这种情况下不需要计算,因为弹出高度为 0 的柱子的时候它的左边没有柱子当着。

所以此时只需要将高度为 0 的柱子弹出然后加入高度为 1 的柱子就行了

也就是最左边没有柱子的时候,我们不需要考虑去计算水的容量

image-20240108194226012

然后加入高度为 0 的柱子

image-20240108194323542

再 下次加入的时候是 高度为 3 的柱子 , 此时需要将 前面两个柱子弹出去

当弹出 高度为 0 的柱子的时候 就可以 根据上面的分析 找到它 左边的 柱子 和 右边的柱子 然后把雨水的容量计算出来

image-20240108194544809

当把高度为 1 的柱子弹出去的时候 它 左边没有柱子了,也不用计算 所以将 这个高度为 1 的柱子 弹出去 然后 加入 高度为 2 的 柱子

image-20240108194859029

下一个 加入 高度为 1 的柱子

image-20240108195552549

下一个 加入 高度为 0 的 柱子

image-20240108195647110

下一个加入 高度为 1 的柱子,此时 违反规则 弹出高度为 0 的 柱子,根据 高度为 0 的柱子的 左边的 柱子 和 右边的柱子 计算出 雨水的容量

image-20240108195812237

然后加入 高度为 1 的柱子

image-20240108195916043

下一个加入 高度为 3 的柱子,此时需要将 前面 3 根柱子都弹出去因为都小于 3,在弹出第一根柱子的时候 此时这根柱子 跟它左边的柱子的高度差是 0 这种情况下不需要考虑雨水,因为它的容量是 0,

image-20240108200631581

然后该弹出 第二根柱子的时候 它左边的柱子 高度是 2 ,此时计算出左边柱子和当前柱子 (3) 中间水的容量

image-20240108200657006

最后高度为 2 的柱子它左边没有柱子了,所以它被弹出后将高度为 3 的柱子加入栈中

image-20240108200818447

下一个加入高度为 2 的柱子

image-20240108200849758

再加入高度为 1 的柱子

image-20240108200913040

下次加入高度为 2 的柱子的时候需要把左边的柱子弹出,此时计算出水的容量然后弹出

image-20240108201000540

下一个加入高度为 1 的柱子,而后续就没有发生高度 违反 规则的 柱子了 所以就不存在计算水的容量问题了

image-20240108201112879

其中蓝色的就是水的总容量了

# 代码:

static class Data
{
   int height; // 高度
   int i;  // 索引
   // 初始化赋值 高 和 宽 的值
   public Data(int height, int i)
   {
      this.height = height;
      this.i = i;
   }
}
static int trap(int[] heights)
{
   LinkedList<Data> stack = new LinkedList<>();
   int sum = 0;
   for (int i = 0; i < heights.length; i++)
   {
      Data right = new Data(heights[i], i);
      // 先判断 栈 是否为空,再判断 如果 栈中柱子的高度 小于 当前要加入的柱子高度 此时违反了规则就需要弹出 小于 当前柱子的柱子
      while (!stack.isEmpty() && stack.peek().height < right.height)
      {   // 弹出 之前的 柱子
         Data pop = stack.pop();
         Data left = stack.peek();
         if (left != null)
         {
            // 当前柱子的索引位置 - 弹出柱子的左边柱子的索引位置 = (差值) 水的宽度 计算水的容量
            int width = right.i - left.i - 1;
            // 当前柱子 和 弹出的左边柱子 取 最小柱子 减去 弹出 柱子的高度 就是 水的高度
            int height = Math.min(left.height, right.height) - pop.height;
            // 高 * 宽 = 面积
            // 累加
            sum += width * height;
         }
      } // 当 违反 规则的柱子弹出完后 就继续加入 下一个柱子
      stack.push(right);
   }
   return sum;
}