「青训营 X 码上掘金」之 攒青豆

86 阅读3分钟

当青训营遇上码上掘金

1. 题目

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

1674319496587.png

以下为上图例子的解析:

输入:height = [5,0,2,1,4,0,1,0,3]

输出:17

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

2. 思路

2.1 单调栈

首先解释下什么是单调栈,顾名思义,就是栈内的元素是单调的,其可以分为单调递增栈和单调递减栈:

  • 单调递增栈:从栈底到栈顶数据是从小到大
  • 单调递减栈:从栈底到栈顶数据是从大到小

递减单调栈为例,如果有一组数,我们依次将元素压入栈,如果当前元素小于等于栈顶元素则入栈,如果大于栈顶元素则先将栈顶不断出栈,直到当前元素小于或等于栈顶元素为止,然后再将当前元素入栈

举个例子,假设这组数为 [5,2,1,4] ,当 [5,2,1] 以单调递减的次序依次入栈后,若 4 想入栈,由于 1<4,故栈顶的 1 出栈;1 出栈后,栈顶元素为 2,由于 2<4,故栈顶的 2 也要出栈;2 出栈后,栈顶元素为 5,由于 5>4,故 4 可以入栈,此时栈内元素为 [5,4]

单调栈则主要用于解决 NGE问题(Next Greater Element),也就是对序列中每个元素,找到下一个比它大的元素(当然,“下一个” 可以换成 “上一个”,“比它大” 也可以换成 “比他小”,原理不变)

2.2 结合单调栈思考题目

观察题之后,我们可以发现实际上要想攒豆,就必须两高夹一低(即形成凹槽),找到左边和右边比本身大的元素,就是利用栈顶栈顶的下一个元素以及要入栈的三个元素来攒豆

所以我们可以通过构建单调(非严格)递减栈来解决这个问题

  • for 循环入栈,过程中将破坏栈递减性的元素出栈

  • 得到两高夹一低的凹槽结构(找左右比自己大的元素)后就计算豆子数量

  • 当前豆子数量 = (“两高”中较矮的那个柱子的高度 - 中间那个“一低”的柱子高度) * 左右柱子的距离

由下图举例,数列为[5,0,2,1,4]

  • 当 [5,0] 依次入栈后,由于 2>0,形成凹槽(502),计算豆子数量为 (2-0) * 1 = 2(即图中画出的第一部分),0 出栈,栈中元素为 [5,2]
  • 1<2,不破坏栈的递减性,故 1入栈,栈中元素为 [5,2,1]
  • 4>1,形成凹槽(214),计算豆子数量为 (2-1) * 1 = 1(即图中画出的第二部分),1 出栈,栈中元素为 [5,2]
  • 4>2,形成凹槽(524),计算豆子数量为 (4-2) * 3 = 6(即图中画出的第三部分),2 出栈,由于 4<5,4 入栈,栈中元素为 [5,4]

故数列 [5,0,2,1,4] 最终可攒 2 + 1 + 6 = 9 颗豆

1674932825643.png

3. 重点代码

用的 C++ 编写的

int greenBean(vector<int>& height) {
    int ans = 0;
    
    // 设置一个栈 
    stack<int> decendStack;
    int length = height.size();
    
    for (int i = 0; i < length; i++) {
    	// 如果栈不空并且当前柱子的高度大于栈顶元素, 则进入循环 
        while (!decendStack.empty() && height[i] > height[decendStack.top()]) {
            int top = decendStack.top();  // 记录栈顶元素的索引值 
            decendStack.pop();  // 栈顶元素出栈 
            
            if (decendStack.empty()) {
                break;
            }
            
            // 记录现在新的栈顶元素的索引值
            int left = decendStack.top();  
            
            // 当前攒的豆子的宽度 
            int curWidth = i - left - 1;
            
            // 当前攒的豆子的高度 
            int curHeight = min(height[left], height[i]) - height[top];
            
            // 计算豆子数量 
            ans += curWidth * curHeight;
        }
        // 将元素入栈 
        decendStack.push(i);
    }
    
    return ans;
}

4. 总结

确实这种方法有点不太好理解,有啥不对的地方感谢指出,笔者也在尽力学习和理解中......