思路一:双指针
时间复杂度为O(n^2)。 空间复杂度为O(1)
列4 左侧最高的柱子是列3,高度为2(以下用lHeight表示)。
列4 右侧最高的柱子是列7,高度为3(以下用rHeight表示)。
列4 柱子的高度为1(以下用height表示)
那么列4的雨水高度为 列3和列7的高度最小值减列4高度,即: min(lHeight, rHeight) - height。
列4的雨水高度求出来了,宽度为1,相乘就是列4的雨水体积了。
(注意第一个柱子和最后一个柱子不接雨水)
class Solution {
public int trap(int[] height) {
int sum=0;
//第一个柱子和最后一个柱子不接雨水
for(int i=1;i<height.length-1;i++){
int rh=height[i];//双指针找左右最高的柱子
int lh=height[i];
for(int r=i+1;r<height.length;r++){
rh=Math.max(rh,height[r]);
}
for(int l=i-1;l>=0;l--){
lh=Math.max(lh,height[l]);
}
int h=Math.min(lh,rh)-height[i];
if(h>0) sum+=h;
}
return sum;
}
}
思路二:动态规划
代替双指针,记录左右最高的柱子
每一个位置的左边最高高度记录在一个数组上(maxLeft),右边最高高度记录在一个数组上(maxRight)
从左向右遍历:maxLeft[i] = max(height[i], maxLeft[i - 1]);
从右向左遍历:maxRight[i] = max(height[i], maxRight[i + 1]);
class Solution {
public int trap(int[] height) {
int sum=0;
int len=height.length;
if(len<=2) return sum;
int[] maxLeft=new int[len];
int[] maxRight=new int[len];
maxLeft[0]=height[0];
maxRight[len-1]=height[len-1];
for (int i = 1; i < len; i++) {
maxLeft[i] = Math.max(height[i], maxLeft[i - 1]);
}
for (int i = len-2; i >= 0; i--) {
maxRight[i] = Math.max(height[i], maxRight[i + 1]);
}
//遍历柱子,求和
for(int i=1;i<len-1;i++){
int temp=Math.min(maxLeft[i],maxRight[i])-height[i];
if(temp>0) sum+=temp;
}
return sum;
}
}
思路三:单调(递减)栈
当前柱子比栈顶高时,当前柱子就是栈顶柱子的【右端】,栈顶的前一个就是【左端】
栈里储存坐标,柱子高度单调递减
class Solution {
public int trap(int[] height) {
Stack<Integer> stack = new Stack<>();
int ans=0;
for(int i=0;i<height.length;i++){
while(!stack.isEmpty() && height[stack.peek()]<height[i]){
int cur=stack.pop();
if(!stack.isEmpty()){
int h=Math.min(height[stack.peek()],height[i])
-height[cur];
int w=i-stack.peek()-1;
ans+=h*w;
}
}
stack.push(i);
}
return ans;
}
}
思路四:顺序模拟
遍历得到当前墙,如果比前一个墙高,加上与前一个墙之间的体积,并接着向前遍历,如果之前有比前一个墙更高的(比当前墙低),再算上增加的体积,最后弹出所有比当前墙低的,以后也不会用上了
注意:每遍历一个墙,要维护一个bottom,接满水的部分就相当于底部升高
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
stack = [] # 记录遍历过的矮墙
for i, h in enumerate(height):
if h == 0:
continue
bottom = 0 # 底部,如果之前的坑填满了雨水,就相当于底部升高了
while stack:
ii, hh = stack[-1]
if h >= hh: # 当前的墙比前面的高
ans += (i - ii - 1) * (hh - bottom)
bottom = hh
# 处理完当前墙的前一面墙
#可以接着向前遍历看又没更高的空间
#将比当前墙低的都弹出,以后也不会用到了
stack.pop()
else: # 当前的墙比前面的矮
ans += (i - ii - 1) * (h - bottom)
break # 不用再向前找更高的墙了
stack.append((i, h))
return ans
class Solution {
public int trap(int[] height) {
int sum=0;
int len=height.length;
Stack<Integer>stack=new Stack<>();
for(int i=0;i<len;i++){
if(height[i]==0) continue;
int bottom=0;
while(stack.size()!=0){
int temp=stack.peek();
if(height[temp]<=height[i]){
sum+=(i-temp-1)*(height[temp]-bottom);
bottom=height[temp];
stack.pop();
}else{//不用再向前找更高的墙了,栈单调递减
sum+=(i-temp-1)*(height[i]-bottom);
break;
}
}
stack.add(i);
}
return sum;
}
}
改编
柱子接雨水,改为木板盛雨水
思路:
- 从左向右遍历,记录当前位置为止最高木板的下标,如果当前木板高于之前最高木板,加上这部分
- 如果最高木板不在最右边,还要再从右向左遍历,操作同上