题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
解法一:动态规划(未建DP table)
图解过程
-
两边都高于当前列,较矮的一边减当前列即可
代码过程 -
先找出右边最高的墙的索引。
-
扫描过去。同时更新左边最高的墙
-
当超过右边最高的墙时,左边最高墙变成右边最高墙,右边最高墙重新计算。
-
依次下去直至扫描到结尾
class Solution {
public int trap(int[] height) {
int n=height.length;
//获取最右最大值索引
int maxIndex=getMaxIndex(height,0);
int max=0,sum=0,tempsum=0;
for(int i=0;i<n;i++){
//若超出最右最大值索引,重置最大值及其索引
if(i>maxIndex){
maxIndex=getMaxIndex(height,i);
max=height[maxIndex];
}
//更新起始节点
if(height[i]>=max){
sum+=tempsum;
tempsum=0;
max=height[i];
}
//较低的墙会积水
if(height[i]<max){
tempsum+=max-height[i];
}
}
return sum;
}
public int getMaxIndex(int[] height,int start){
int maxIndex=0,max=0;
for(int i=start;i<height.length;i++){
if(height[i]>=max){
max=height[i];
maxIndex=i;
}
}
return maxIndex;
}
}
代码优化,两次遍历
class Solution {
public int trap(int[] height) {
int n=height.length;
//获取最右最大值索引
int maxIndex=getMaxIndex(height,0);
int max=0,sum=0,tempsum=0;
//从左往右
for(int i=0;i<maxIndex;i++){
//更新起始节点
if(height[i]>max){
sum+=tempsum;
tempsum=0;
max=height[i];
}
//较低的墙会积水
if(height[i]<max){
tempsum+=max-height[i];
}
}
max=0;
//从右往左到达最大值
for(int i=n-1;i>maxIndex;i--){
if(height[i]>=max){
sum+=tempsum;
tempsum=0;
max=height[i];
}
if(height[i]<max){
tempsum+=max-height[i];
}
}
return sum+tempsum;
}
public int getMaxIndex(int[] height,int start){
int maxIndex=0,max=0;
for(int i=start;i<height.length;i++){
if(height[i]>=max){
max=height[i];
maxIndex=i;
}
}
return maxIndex;
}
}
优化代码,找最大值放一块
class Solution {
public int trap(int[] height) {
int n=height.length;
int tempmax=0,maxIndex=0;
for(int i=0;i<n;i++){
if(height[i]>=tempmax){
tempmax=height[i];
maxIndex=i;
}
}
int max=0,sum=0,tempsum=0;
for(int i=0;i<maxIndex;i++){
if(height[i]>max){
sum+=tempsum;
tempsum=0;
max=height[i];
}
if(height[i]<max){
tempsum+=max-height[i];
}
}
max=0;
for(int i=n-1;i>maxIndex;i--){
if(height[i]>=max){
sum+=tempsum;
tempsum=0;
max=height[i];
}
if(height[i]<max){
tempsum+=max-height[i];
}
}
return sum+tempsum;
}
}
解法二:动态规划(建立DP table)
class Solution {
public int trap(int[] height) {
int n=height.length;
int[] left_height=new int[n];
int[] right_height= new int[n];
for(int i=1;i<n;i++){
left_height[i]=Math.max(left_height[i-1],height[i-1]);
}
for(int i=n-2;i>=0;i--){
right_height[i]=Math.max(right_height[i+1],height[i+1]);
}
int sum=0;
for(int i=1;i<n-1;i++){
if(height[i]<left_height[i]&&height[i]<right_height[i]){
sum+=Math.min(left_height[i],right_height[i])-height[i];
}
}
return sum;
}
}
优化,去掉一个数组
class Solution {
public int trap(int[] height) {
int n=height.length;
int left_max=0,sum=0;
int[] right_max= new int[n];
for(int i=n-2;i>=0;i--){
right_max[i]=Math.max(right_max[i+1],height[i+1]);
}
for(int i=1;i<n-1;i++){
left_max=Math.max(left_max,height[i-1]);
if(height[i]<left_max&&height[i]<right_max[i]){
sum+=Math.min(left_max,right_max[i])-height[i];
}
}
return sum;
}
}
优化为双指针
- 哪边墙低,更新哪边墙
class Solution {
public int trap(int[] height) {
int n=height.length;
int left=1,right=n-2,sum=0;
int left_max=0,right_max=0;
while(left<=right){
if(height[left-1]<height[right+1]){
left_max=Math.max(left_max,height[left-1]);
if(left_max>height[left]){
sum+=left_max-height[left];
}
left++;
}
else{
right_max=Math.max(right_max,height[right+1]);
if(right_max>height[right]){
sum+=right_max-height[right];
}
right--;
}
}
return sum;
}
}
解法三:栈
- 当低于或等于栈顶,进入栈,cur++;
- 高于栈顶,弹出栈顶,若栈非空,计算距离,sum+=距离* 两侧最低高度
- 继续循环,直至栈为空或低于等于栈顶,跳出循环
class Solution {
public int trap(int[] height) {
int n=height.length,cur=0;
Stack<Integer> stack=new Stack<>();
while(cur<n){
while(!stack.isEmpty()&&height(cur)>height[stack.peek()]){
int h=height[stack.peek()];
stack.pop();
if(stack.isEmpty()){
break;
}
int distance=cur-stack.peek()-1;
int min=Math.min(height[stack.peek()],height[cur]);
sum+=distance*(min-h);
}
stack.push(cur);
cur++;
}
return sum;
}
}