当青训营遇上码上掘金
1、题目
现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)
以下为上图例子的解析:
输入:height = [5,0,2,1,4,0,1,0,3]
输出:17
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。
2、单调栈解题思路
单调栈的经典题目,接雨水的改编版。 首先,接雨水需要三个柱子,即形成凹槽,所以左右柱子肯定大于中间的柱子。要找到这样的组合,就想到了单调栈。
- 单调栈:从栈尾到栈头从大到小,
- 关键点是如果当前要添加的元素大于栈头元素,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。
- 第二个关键点是如何计算雨水面积,设从左向右三个柱子高度分别为left,mid,right, 则高度h = min(left, right) - mid, 宽度w = right_index - left_index - 1;
- 因为可能会遇到5 4 3 2 5 这种凹槽,所以需要循环判断,不断填充短的凹槽,如上,先填充3 2 5 的凹槽组合,然后是4 3 5, 最后是5 4 5的组合凹槽。
3、c++ 代码
#include<iostream>
#include <vector>
#include <stack>
using namespace std;
//单调栈思想
//看做三个柱子构成的凹槽,单调栈从尾部到栈头,从大到小
int fun(vector<int> height){
stack<int> st;
if(height.size() <= 2) return 0;
//st存放索引
st.push(0);
int ans = 0;
for(int i = 1; i < height.size(); i++){
int num = height[i];
while(!st.empty() && num > height[st.top()]){
//num 为右边界,top()为中间,第二个栈顶元素为左边界
//mid 为索引位置
int mid = st.top();
st.pop();
if(!st.empty()){
int h = min(num, height[st.top()]) - height[mid];
int w = i - st.top() - 1;
ans += (h * w);
}
}
st.push(i);
}
return ans;
}
int main() {
vector<int> height = {5,0,2,1,4,0,1,0,3};
cout<<fun(height)<<endl;
return 0;
}
链接: code.juejin.cn/api/raw/718…
4、复杂度分析
空间 O(n) ; 时间 O(n) , n 为输入数组规模
5、其他方案补充
-
双指针思想: 核心思想即对于每一列柱子,找出其两侧最高的柱子,就可以得到它能存储的雨水量。注意第一个柱子和最后一个柱子不能接水,不计算。时间复杂度O(n^2)
-
进一步优化: 为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。我们把每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)。这样就避免了重复计算,这就用到了动态规划。
-
动态规划:当前位置,左边的最高高度是前一个位置的左边最高高度和本高度的最大值。
即从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
时间和空间复杂度O(n)。