42. 接雨水

给定 n 个非负整数表示每个宽度为 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 个单位的雨水(蓝色部分表示雨水)

 

双指针解法

按照列来计算的话,宽度一定是1了,我们再把每一列的雨水的高度求出来就可以了。

可以看出每一列雨水的高度,取决于,该列 左侧最高的柱子和右侧最高的柱子中最矮的那个柱子的高度

即: min(lHeight, rHeight) - height

class Solution {
    public int trap(int[] height) {
        return process(height);
    }

    private int process(int[] height){
        //按列计算
        //i:left Max. rightMax. h= min(leftMax,rightMax)-heigt[i]>0 sum+=h
        if(height.length<=2){
            return 0;
        }
        int sum=0;
        for(int i=1;i<height.length-1;i++){
            int leftMax=getRangeMax(height,0,i);
            int rightMax=getRangeMax(height,i,height.length-1);
            int h=Math.min(leftMax,rightMax)-height[i];
            if(h>0){
                sum+=h;
            }
            
        }
        return sum;
    }

    private int getRangeMax(int[] height,int L,int R){
        int max=0;
        for(int i=L;i<=R;i++){
            max=Math.max(max,height[i]);
        }
        return max;
    }
}

  

动态规划解法

为了得到两边的最高高度,使用了双指针来遍历,每到一个柱子都向两边遍历一遍,这其实是有重复计算的。

我们把每一个位置的左边最高高度记录在一个数组上(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) {
        return process(height);
    }

    private int process(int[] height){
       //leftMax[i] 0....i 求得每个位置左边的最大值 leftMax[i]=Math.max(dp[i-1],height[i])
       //rightMax[i] i....R 求得每个位置右边的最大值 rightMax[i]=Maht.max(dp[i+1],heigth[i])
       //遍历1....i-1. Math.min(leftMax(i),rightMax(i))-heigt[i]
       //h >0 sum+=h
       int n=height.length;
       if(n<=2){
           return 0;
       }
       int[] leftMax=new int[n];
       leftMax[0]=height[0];
       for(int i=1;i<n;i++){
           leftMax[i]=Math.max(leftMax[i-1],height[i]);
       }

       int[] rightMax=new int[n];
       rightMax[n-1]=height[n-1];
       for(int i=n-2;i>=0;i--){
           rightMax[i]=Math.max(rightMax[i+1],height[i]);
       }

       int sum=0;
       for(int i=1;i<n-1;i++){
           int h=Math.min(leftMax[i],rightMax[i])-height[i];
           if(h>0) sum+=h;
       }

       return sum;
       
    }


}

  

单调栈解法

单调栈是按照行方向来计算雨水

 

从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序。

因为一旦发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子。

遇到相同高度的柱子怎么办。

遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中。

例如 5 5 1 3 这种情况。如果添加第二个5的时候就应该将第一个5的下标弹出,把第二个5添加到栈中。

因为我们要求宽度的时候 如果遇到相同高度的柱子,需要使用最右边的柱子来计算宽度。

class Solution {
    public int trap(int[] height) {
        if(height.length<=2){
            return 0;
        }
        return process(height);
    }

    private int process(int[] height){
      //stack bigger.....smaller. when i the bigger then peek(),then pop,
      //i,popi,peeki, Math.min(height[i],hight[stack.peek()])-height[popi]-->h
      //w=i-peeki-1
      //h*w
      int sum=0;
      Stack<Integer> stack=new Stack<>();
      stack.push(0);
      for(int i=1;i<height.length;i++){
        
          if(height[i]<height[stack.peek()]){
              stack.push(i);
          }else if(height[i]==height[stack.peek()]){
              stack.pop();
              stack.push(i);
          }else{
              while(!stack.isEmpty() && height[i]>height[stack.peek()]){
                  int popi=stack.pop();
                  if(!stack.isEmpty()){
                      int h=Math.min(height[i],height[stack.peek()])-height[popi];
                      int w=i-stack.peek()-1;
                      int hold=h*w;
                      if(hold>0) sum+=hold;
                  }
               
              }
              stack.push(i);
          }
      }
      return sum;
    }


}

  

 

posted @ 2021-11-23 13:12  sherry001  阅读(26)  评论(0编辑  收藏  举报