当青训营遇上码上掘金
主题 4:攒青豆
现有 个宽度为 的柱子,给出 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
借用一下图片~
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
思路1:单调栈
本题有非常多的解法,首先是单调栈的做法~
对于本题,我们可以维护一个栈,栈中存储的是 数组的下标,对应的值是 不严格单调递减 的。这里的 不严格 指的是栈中存储的 数组的下标对应的高度可能是相等的。
假设栈顶的元素下标为 ,栈顶元素的上一个元素的下标为 ,此时满足 , 那么就会形成一个 高度的坑,记为 ,坑的长度为 ,记为
因此这个坑内累积的绿豆就为
最后总的累积的绿豆就是所有这些坑累积的绿豆之和
时间复杂度为
简单版本的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;
}