当青训营遇上码上掘金,由于主题4的题目起的比较有意思,于是作者毫不犹豫地选择了主题四。
题目
攒青豆 现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入: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存的水不仅仅由他的头上那么点,主要是指示着两边的一个情况),它的高度根据第一条可知,是左边和右边最短的一个柱子高度再减去柱子本身的高度。
- 综上,栈顶和栈顶的下一个元素以及要入栈的三个元素来接水
- 上面分析可知,我们需要在单调栈内不仅仅保存高度,还要保存宽度。而这里直接保存下标,即可完成高度和宽度的双重保存!(具体实现看代码)
-
遇到相同高度的柱子怎么办
- 遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。
- 因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。
#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;
}