当青训营遇上码上掘金之“攒青豆”|「青训营 X 码上掘金」主题创作

68 阅读2分钟

当青训营遇上码上掘金

题目

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

例子

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

1_副本.png

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

解题思路——单调栈

单调栈指的从栈底到栈顶的元素是单调变化的栈。我们可以使用一个存储下标的栈满足从栈底到栈顶的下标对应的数组 height 中的元素递减。

此时我们遍历数组,假设到下标 i 时,栈内至少有两个元素,记栈顶元素为 top,如果当前柱子比栈顶柱子高,则弹出栈顶元素并计算出被这两根柱子围成的区域能够盛放的青豆数量,重复上述操作,直到栈变为空,或者栈顶下标对应的高度大于或等于 height[i]。之后将 i 入栈,继续遍历下标,最后就能得到能盛的青豆数量。

代码

我们根据以上的解题思路可以得到相应的代码 (以下以 C++ 代码为例):

int getQingdou(vector<int>& height) {
    int n = height.size();

    int ans = 0;
    // 设置一个单调栈
    stack<int> s;
    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 left = s.top();
            int currWidth = i - left - 1;
            int currHeight = min(height[left], height[i]) - height[top];
            ans += currWidth * currHeight;
        }
        
        // 计算完毕后将 i 压入栈
        s.push(i);
    }
    return ans;
}

复杂度分析

  • 时间复杂度:O(n),从 0 到 n−1 的每个下标最多只会入栈和出栈各一次。
  • 空间复杂度:O(n),空间复杂度主要取决于栈空间,栈的大小不会超过数组 height 的长度 n。

结尾

本文主要对 “攒青豆” 问题给出了一种单调栈的解法,除此之外存在的其他解法暂不赘述,欢迎大家一起讨论学习。