「青训营 X 码上掘金」主题4——攒青豆

39 阅读2分钟

当青训营遇上码上掘金

主题介绍

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

攒青豆.png

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

解题思路

我们可以思考,从左往右,如果遇到一个柱子比之前的高,那么就代表这个柱子与左边的空间可以容纳一些青豆,但是我们需要一些更多的信息保存,例如在柱子5和4之间容纳完青豆后,2和1柱子就不再是有效信息了,但是5和4柱子仍然是需要被保存的,因为右侧可能会有比4和5柱子更高的柱子,但是4高度一下的空间已经被填充过了,这样我们可以看出来我们需要维护一个单调下降的柱子序列,一旦遇到更高的柱子,就要逐步清除保存过的柱子并计算。例如[5,2,1]遇到4后变成[5,4]。需要注意的是,没有柱子,即高度0,也是需要考虑在内的。

对于这个数据结构,我们可以使用单调栈来存储柱子的位置,保持这个栈对于柱子的高度从栈顶往下单调递减,并且解决如何清除保存过的柱子,也就是退栈时进行计算的问题。

当栈中至少有两个元素时,记第一个为toptop,第二个为lastlast,当遇到height[i]>height[top]height[i]>height[top]时,开始进行计算,宽度wwilast1i-last-1,高度hhmin(height[last],height[i])height[top]min(height[last], height[i])-height[top]ans+=whans+=w*h即可。进行完这些计算并退栈后,加入当前元素到栈中时可以保持单调性。

假设例子中i从1到n遍历,当i=3i=3,栈存储[1,2]时,遇到2,这时进行退栈,top=0,last=5,w=ilast1=311=1,h=min(height[1],height[3])height[1]=min(5,2)0=2,wh=12=2top=0,last=5,w=i-last-1=3-1-1=1,h=min(height[1], height[3])-height[1]=min(5,2)-0=2,w*h=1*2=2

如图展示了几次计算的顺序:

1.png

代码实现

#include <iostream>
#include <stack>
#include <vector>

using namespace std;
stack<int> s;
vector<int> height;
int n, ans;
int main() {
    cin >> n;
    for(int i = 0, h; i < n; i++) {
    	cin >> h;
    	height.push_back(h);
    }
    for(int i = 0; i < n; ++i) {
        while (!s.empty() && height[i] > height[s.top()]) {
            int top = s.top(); s.pop();
            if (s.empty()) break;
            int last = s.top();
            int w = i - last - 1;
            int h = min(height[last], height[i]) - height[top];
            ans += w * h;
        }
        s.push(i);
    }
    cout << ans << endl;
    return 0;
}