主题 4:攒青豆

181 阅读2分钟

当青训营遇上码上掘金

主题 4:攒青豆

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

借用一下图片~

攒青豆.png

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

思路1:单调栈

本题有非常多的解法,首先是单调栈的做法~

对于本题,我们可以维护一个栈,栈中存储的是 heightheight 数组的下标,对应的值是 不严格单调递减 的。这里的 不严格 指的是栈中存储的 heightheight 数组的下标对应的高度可能是相等的。

假设栈顶的元素下标为 toptop,栈顶元素的上一个元素的下标为 leftleft ,此时满足 height[stack[left]]height[stack[top]]height[stack[left]] \ge height[stack[top]] , 那么就会形成一个 min(height[stack[left]],height[i])height[stack[top]]min(height[stack[left]], height[i]) - height[stack[top]] 高度的坑,记为 hh,坑的长度为 istack[left]1i - stack[left] - 1 ,记为 ww

因此这个坑内累积的绿豆就为 whw * h

最后总的累积的绿豆就是所有这些坑累积的绿豆之和

时间复杂度为 O(n)O(n)

简单版本的C++实现如下~~~

#include<iostream>
#include<algorithm>
#include<vector>
int main() {
    int x;
    std::vector<int> height;
    while (std::cin >> x) {
        height.push_back(x);
    }
    int n = height.size();
    std::vector<int> left;
    int res = 0;
    for (int i = 0; i < n; i++) {
        while ((int)left.size() > 0 && height[left.back()] < height[i]) {
            int hh = height[left.back()];
            left.pop_back();
            if ((int)left.size() == 0) break;
            int h = std::min(height[left.back()], height[i]) - hh;
            int w = i - left.back() - 1;
            res += w * h;
        }
        left.push_back(i);
    }
    std::cout << res << std::endl;
}

思路2:前后缀分解

考虑每一个位置能够装下的绿豆(就是按列求啦~),对于一列,能够装下的绿豆就是其 左边的最大高度和右边的最大高度中的最小值,减去这一列自身的高度

为了实现 i 位置的左边和右边的最大高度,我们需要额外维护两个数组 pre,post ,分别表示前缀最大高度和后缀最大高度

这种做法的正确性是显然的,因为对于每一列来说,只有当这一列的左边和右边同时存在比他大的数,才会堆积绿豆,并且如果存在,那么这一列能够装下的绿豆,就是左边和右边的最小高度减去自身的高度。

简单版本的C++实现如下~~~

#include<iostream>
#include<algorithm>
#include<vector>
int main() {
    int x;
    std::vector<int> height;
    while (std::cin >> x) {
        height.push_back(x);
    }
    int res = 0, n = height.size();
    std::vector<int> pre(n, 0), post(n, 0);
    for (int i = 0; i < n; i++) {
        pre[i] = height[i];
        if (i > 0) pre[i] = std::max(pre[i - 1], pre[i]);
    }
    for (int i = n - 1; i >= 0; i--) {
        post[i] = height[i];
        if (i < n - 1) post[i] = std::max(post[i + 1], post[i]);
    }
    for (int i = 0; i < n; i++) {
        res += std::min(pre[i], post[i]) - height[i];
    }
    std::cout << res << std::endl;
}