「青训营 X 码上掘金」主题创作活动——攒青豆

83 阅读2分钟

当青训营遇上码上掘金

题目

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积),不得不说非常贴切活动主题hh,大家一起来卷青豆吧! image.png

解题思路

只有类似于“凹”型区域才能接住青豆,如果柱子的高度一直是单调递增或单调递减的,则无法接住青豆,而一旦先递减再递增时,则形成凹形区域,能够接住青豆,故可以使用单调栈来模拟该问题。创建一个单调递减栈,当柱子的高度是单调递减时,入栈;当出现某个柱子比栈顶柱子高时,说明此时可以攒一些青豆了。

到现在为止,确定了更新青豆数量的时机,需要进一步处理的,就是如何计算的问题了。当单调递减栈有两个及两个以上元素时,若遇到比栈顶柱子高的柱子时,栈顶第二个柱子m、栈顶柱子n和新遇到的柱子k构成一个“凹”型,凹形区域能够攒的青豆数则为 cnt = (k - m - 1) * [min(heightm, heightk) - heightn],该凹形区域填满青豆后,从上空往下看,柱子n不会再影响后续攒豆子的行为,故出栈即可。

单调递减栈遇到一个比栈顶柱子高的柱子时,更新一次青豆数量,此时栈顶柱子出栈,为了保持单调递减栈的性质,继续比较现存的栈顶柱子,迭代进行,直到比栈顶柱子矮或者栈为空,执行入栈操作。

现在还有一个问题就是,似乎程序执行完,栈中可能还有元素,这是否合理呢?答案是合理的,因为从高空往下看,现存的柱子是单调递减的,而这又是无法构成凹形区域来攒豆子的。

代码

#include <iostream>
#include <vector>
#include <stack>
using namespace std;

int trap(vector<int>& height) 
{
    int n = height.size(), result = 0;
    stack<int> s;
    for(int i = 0; i < n; i++)
    {
        while(!s.empty() && height[s.top()] < height[i])
        {
            int left = s.top();
            s.pop();
            if(s.empty()) break;
            int h = min(height[i], height[s.top()]) - height[left];
            result += h * (i - s.top() - 1);
        }
        s.push(i);
    }
    return result;
}

int main() 
{
    vector<int> height = {5, 0, 2, 1, 4, 0, 1, 0, 3};
    cout << trap(height) << endl;
    return 0;
}