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

71 阅读3分钟

当青训营遇上码上掘金,由于主题4的题目起的比较有意思,于是作者毫不犹豫地选择了主题四。

题目

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

image.png

以下为上图例子的解析: 
输入:height = [5,0,2,1,4,0,1,0,3] 
输出:17 
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

题目解析

由于当后面的柱子高度比前面的低时,是无法接雨水的,因此本题是一个很明显找后面第一个更高柱子的单调递减栈问题。

由于积攒豆子单位是一个面积,因此我们需要从高度和宽度两个方面分析问题:

  • 【高度】而从图中我们也可以得知,积攒豆子的多少,是取决于左右主子之间最短的那个柱子(木桶定律--一只水桶能装多少水取决于它最短的那块木板)。而找左右,则接下来分析宽度

  • 【宽度】一个柱子按理来说,可以轻松得到它的垂直方向上的攒豆量(高度 * 1),但是并不能保证这个柱子左边会不会有更小的从而造成漏豆子。如上图,如果假设没有5,那么2的左边就不会积攒豆子。因此我们还需要进一步考虑一个柱子的左边什么情况

    • 如果左边的柱子更低,那么代表漏豆子,则直接退出计算。
    • 如果左边的柱子更高,那么能有储豆子,则宽度为左边和右边柱子的距离(这里需要理解一下,就如上图的2,此时如下图的地方肯定被已经算了,因此2存的水不仅仅由他的头上那么点,主要是指示着两边的一个情况),它的高度根据第一条可知,是左边和右边最短的一个柱子高度再减去柱子本身的高度。
    • 综上,栈顶和栈顶的下一个元素以及要入栈的三个元素来接水

image.png

  • 上面分析可知,我们需要在单调栈内不仅仅保存高度,还要保存宽度。而这里直接保存下标,即可完成高度和宽度的双重保存!(具体实现看代码)
  • 遇到相同高度的柱子怎么办

    • 遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
    • 因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。
#include <bits/stdc++.h>
using namespace std;

int SaveBeans(vector<int>& height) {
    int n = height.size(), ans = 0;
    stack<int>sk;
    for(int i = 0; i < n; ++i) {
        while(!sk.empty() && height[sk.top()] <= height[i]) {
            int cur = sk.top();
            sk.pop();
            if(sk.empty()) break; // 左边漏豆子
            int l = sk.top(), r = i;
            int h = min(height[l], height[r]) - height[cur];
            ans += h * (r - l - 1);
        }
        sk.push(i); 
    }
    return ans;
}

int main() {
    int tem = 0;
    vector<int>height;
    //输入
    while(cin >> tem) {
        height.push_back(tem);
        //处理
        if(cin.get() == '\n') {
            int ans = SaveBeans(height);
            cout << ans << endl;
            height.clear();
        }
    }
    return 0;
}