当青训营遇上码上掘金之“攒青豆”

61 阅读1分钟

题目: 现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

动态规划

创建两个长度为 n 的数组 leftMax 和 rightMax。对于 0≤i<n,leftMax[i] 表示下标 iii 及其左边的位置中,height 的最大高度,rightMax[i] 表示下标 i 及其右边的位置中,height 的最大高度。显然,leftMax[0]=height[0],rightMax[n−1]=height[n−1]。而其余下标则满足:

image.png 对于 0≤i<n,下标 i 处能接的雨水量等于 min⁡(leftMax[i],rightMax[i])−height[i]。而leftMax和rightMax则可通过遍历height得到。

public int getBeans(int[] height) {
    int n = height.length;
    if (n == 0) {
        return 0;
    }

    int[] leftMax = new int[n];
    leftMax[0] = height[0];
    for (int i = 1; i < n; ++i) {
        leftMax[i] = Math.max(leftMax[i - 1], height[i]);
    }

    int[] rightMax = new int[n];
    rightMax[n - 1] = height[n - 1];
    for (int i = n - 2; i >= 0; --i) {
        rightMax[i] = Math.max(rightMax[i + 1], height[i]);
    }

    int ans = 0;
    for (int i = 0; i < n; ++i) {
        ans += Math.min(leftMax[i], rightMax[i]) - height[i];
    }
    return ans;
}

单调栈

理解题目,注意题目的性质,当后面的柱子高度比前面的低时,是无法接雨水的 。当找到一根比前面高的柱子,就可以计算接到的雨水,所以使用单调递减栈。

对更低的柱子入栈: 更低的柱子以为这后面如果能找到高柱子,这里就能接到雨水,所以入栈把它保存起来

平地相当于高度 0 的柱子,没有什么特别影响

当出现高于栈顶的柱子:说明可以对前面的柱子结算了

计算已经到手的雨水,然后出栈前面更低的柱子 计算雨水的时候需要注意的是:雨水区域的右边 r 指的自然是当前索引 i

  1. 底部是栈顶 st.top() ,因为遇到了更高的右边,所以它即将出栈,使用 cur 来记录它,并让它出栈
  2. 左边 l 就是新的栈顶 st.top()
  3. 雨水的区域全部确定了,水坑的高度就是左右两边更低的一边减去底部,宽度是在左右中间
  4. 使用乘法即可计算面积
public int getBeans(int[] walls) {
        if (walls == null || walls.length <= 2) {
            return 0;
        }

        int bean = 0;
        Stack<Integer> stack = new Stack<>();
        for (int right=0; right<walls.length; right++) {
            // 栈不为空,且当前元素(右墙)比栈顶(右墙的左侧)大:说明形成低洼处了
            while (!stack.isEmpty() && walls[right]>walls[stack.peek()]) {
                int bottom = stack.pop();
                // 看看栈里还有没有东西(左墙是否存在)
                // 有右墙+有低洼+没有左墙=白搭
                if (stack.isEmpty()) {
                    break;
                }

                // 左墙位置以及左墙、右墙、低洼处的高度
                int left = stack.peek();
                int leftHeight = walls[left];
                int rightHeight = walls[right];
                int bottomHeight = walls[bottom];

                // 能积攒的豆子=(右墙位置-左墙位置-1) * (min(右墙高度, 左墙高度)-低洼处高度)
                bean += (right-left-1) * (Math.min(leftHeight, rightHeight)-bottomHeight);
            }

            // 上面的pop循环结束后再push,保证stack是单调不增
            stack.push(right);
        }

        return bean;
    }
```
```