题目描述:
给定 n n n 个非负整数表示每个宽度为 1 1 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例一:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例二:
输入:height = [4,2,0,3,2,5]
输出:9
思路一:单调栈
这种柱状图类似的题,往往用单调栈。在栈里面维护一个单调下降的序列,
- 若是遇到比栈顶元素更小的,根据栈顶元素计算它的 ”贡献“ ,然后直接入栈。
- 若是遇到比栈顶元素更大的,不断弹出栈顶元素,同时计算 ”贡献“,直到符合条件 1 时,按上一条方法计算。
//单调栈
class Solution {
public:
int trap(vector<int>& h) {
const int N=2e4+10;
int st[N],tot=0,ans=0;
for(int i=0;i<h.size();i++){
int pre=0;
while(tot&&h[i]>=h[st[tot-1]]){
ans+=(h[st[tot-1]]-pre)*(i-st[tot-1]-1);
pre=h[st[tot-1]];
tot--;
}
if(tot&&h[i]<h[st[tot-1]]){
ans+=(h[i]-pre)*(i-st[tot-1]-1);
}
st[tot++]=i;
}
return ans;
}
};
思路二:前缀和
设, h l i hl_i hli 为第 i i i 列左边最高的墙, h r i hr_i hri 为第 i i i 列右边最高的墙。
计算某一列中能储水的高度,需要知道 m i n ( h l i , h r i ) min(hl_i,hr_i) min(hli,hri)
可以用前缀和先预处理, O ( n ) O(n) O(n) 求出所有的 h l i hl_i hli 和 h r i hr_i hri ,最后再 O ( n ) O(n) O(n) 求出答案。
//前缀后缀
class Solution {
public:
int trap(vector<int>& h) {
int n=h.size(),ans=0;
vector<int> l(n),r(n);
l[0]=r[0]=h[0];
l[n-1]=r[n-1]=h[n-1];
for(int i=1;i<=n-2;i++){
l[i]=max(l[i-1],h[i]);
r[n-1-i]=max(r[n-i],h[n-1-i]);
}
for(int i=1;i<=n-2;i++){
int x=min(l[i-1],r[i+1]);
if(x>h[i]) ans+=x-h[i];
}
return ans;
}
};
思路三:双指针
在思路二的基础上,要计算第 i i i 列的储水量,其实不需要知道 h l i hl_i hli 和 h r i hr_i hri,只需知道 m i n ( h l i , h r i ) min(hl_i,hr_i) min(hli,hri) 即可。
两个指针初始分别指向最左边和最右边,每次移动 ”最高的墙更低一些“ 那个方向的指针,遍历一遍即可。
//双指针
class Solution {
public:
int trap(vector<int>& h) {
int l=0,r=h.size()-1,hl=h[l],hr=h[r],ans=0;
while(l<r){
while(l<r&&hl<=hr){
hl=max(hl,h[++l]);
int x=min(hl,hr);
if(x>h[l]) ans+=x-h[l];
}
while(l<r&&hl>hr){
hr=max(hr,h[--r]);
int x=min(hl,hr);
if(x>h[r]) ans+=x-h[r];
}
}
return ans;
}
};
复杂度:
时间复杂度都是 O ( n ) O(n) O(n)。双指针的空间复杂度 O ( 1 ) O(1) O(1),其他两个空间复杂度 O ( n ) O(n) O(n)。