题目描述:
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
方法 1:暴力
直观想法
直接按问题描述进行。对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。
算法
初始化 ans=0
从左向右扫描数组:
初始化max_left=0 和max_right=0
从当前元素向左扫描并更新:max_left=max(max_left,height[j])
从当前元素向右扫描并更新:max_right=max(max_right,height[j])
将min(max_left,max_right)−height[i] 累加到 ans
代码:
class Solution {
public int trap(int[] height) {
int ans=0;
int size=height.length;
for (int i=1;i<size-1;i++){
int max_left=0,max_right=0;
for (int j=i;j>=0;j--){
max_left=Math.max(max_left,height[j]);
}
for (int j=i;j<size;j++){
max_right=Math.max(max_right,height[j]);
}
ans+=Math.min(max_left,max_right)-height[i];
}
return ans;
}
}
方法 2:动态编程
直观想法
在暴力方法中,我们仅仅为了找到最大值每次都要向左和向右扫描一次。但是我们可以提前存储这个值。因此,可以通过动态编程解决。
这个概念可以见下图解释:
算法
找到数组中从下标 i 到最左端最高的条形块高度 left_max。
找到数组中从下标 i 到最右端最高的条形块高度 right_max。
扫描数组height 并更新答案:
累加 min(max_left[i],max_right[i])−height[i] 到 ans 上
public int trap(int[] height) {
if (height==null||height.length==0)
return 0;
int ans=0;
int size=height.length;
int[] left_max=new int[size];
int[] right_max=new int[size];
left_max[0]=height[0];
for (int i=1;i<size;i++){
left_max[i]=Math.max(height[i],left_max[i-1]);
}
right_max[size-1]=height[size-1];
for (int i=size-2;i>=0;i--){
right_max[i]=Math.max(height[i],right_max[i+1]);
}
for (int i=1;i<size-1;i++){
ans+=Math.min(left_max[i],right_max[i])-height[i];
}
return ans;
}
方法 3:栈的应用
直观想法
我们可以不用像方法 2 那样存储最大高度,而是用栈来跟踪可能储水的最长的条形块。使用栈就可以在一次遍历内完成计算。
我们在遍历数组时维护一个栈。如果当前的条形块小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。如果我们发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到 ans 。
代码:
public int trap(int[] height) {
int ans=0,current=0;
Stack<Integer> stack=new Stack<>();
while (current<height.length){
while (!stack.empty()&&height[current]>height[stack.peek()]){
int top= stack.pop();
if (stack.empty())
break;
int distance=current-stack.peek()-1;
int bounded_height=Math.min(height[current],height[stack.peek()])-height[top];
ans+=distance*bounded_height;
}
stack.push(current++);
}
return ans;
}