当青训营遇上码上掘金
主题介绍
- 现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入: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,也是需要考虑在内的。
对于这个数据结构,我们可以使用单调栈来存储柱子的位置,保持这个栈对于柱子的高度从栈顶往下单调递减,并且解决如何清除保存过的柱子,也就是退栈时进行计算的问题。
当栈中至少有两个元素时,记第一个为,第二个为,当遇到时,开始进行计算,宽度为,高度为,即可。进行完这些计算并退栈后,加入当前元素到栈中时可以保持单调性。
假设例子中i从1到n遍历,当,栈存储[1,2]时,遇到2,这时进行退栈,。
如图展示了几次计算的顺序:
代码实现
#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;
}