算法-接雨水问题

盛最多水的容器(经典)

添加链接描述
解法:
左右指针初始指向数组两端点,记录此时的容量。
每次将高度小的指针向中间移动,每移动一位计算一次容量。
直到相遇,最大容量即为所求!

证明此法可行:
关键是这句:每次将高度小的指针向中间移动
因为此时数字较小的那个指针不可能再作为容器的边界了。所以废弃这个位置向中间移动。
为什么不可能再作为容器的边界了?证明:
在这里插入图片描述
代码:

public class Solution {
    
    
    public int maxArea(int[] height) {
    
    
        int max=-1;
        int i=0,j=height.length-1;
        while (i<j){
    
    
            int area= Math.min(height[i],height[j])*(j-i);

            max=Math.max(max,area);

            if(height[i]<=height[j]){
    
    
                i++;
            }else {
    
    
                j--;
            }
        }
        return max;
    }
}

接雨水

题目链接

暴力解法 时间n^2 空间1

对于这种问题,我们不要想整体,而应该去想局部

具体来说,对于下图位置i,能装下多少水呢?能装 2 格水
在这里插入图片描述

为什么位置i最多能盛 2 格水呢?因为,位置i能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关,我们分别称这两个柱子高度为l_max和r_max;位置 i 最大的水柱高度就是min(l_max, r_max)。

更进一步,对于位置i,能够装的水为:

water[i] = min(
               # 左边最高的柱子
               max(height[0..i]),  
               # 右边最高的柱子
               max(height[i..end]) 
            ) - height[i]

在这里插入图片描述
代码:

class Solution {
    
    
    public int trap(int[] height) {
    
    
        int ans=0;
        int len=height.length;


        for (int i=1;i<len-1;i++){
    
     //左右两端不可能装水
            int l_max=0,r_max=0; 
            //计算左最高
            for(int j=0;j<=i;j++){
    
     
                l_max=Math.max(l_max,height[j]);
            }

            //计算右最高
            for(int j=i;j<len;j++){
    
     
                r_max=Math.max(r_max,height[j]);
            }
            
            ans+=Math.min(l_max,r_max)-height[i];// 如果自己就是最高的话,l_max == r_max == height[i]
        }
        return ans;
    }
}

备忘录优化 时间n 空间n

暴力解法中,对每个位置i都要计算r_max和l_max,存在大量重复计算。

优化:开两个数组r_max和l_max充当备忘录,l_max[i]表示位置i左边最高的柱子高度,r_max[i]表示位置i右边最高的柱子高度。预先把这两个数组计算好,避免重复计算。

代码:

class Solution {
    
    
    public int trap(int[] height) {
    
    
        if(height.length==0) return 0;
        int ans=0;
        int len=height.length;

        int[] l_max=new int[len];
        int[] r_max=new int[len];

        l_max[0]=height[0];
        r_max[len-1]=height[len-1];

        //计算左边最大
        for (int i=1;i<len;i++ ){
    
    
            l_max[i]=Math.max(l_max[i-1],height[i]);
        }
        //计算右边最大
        for (int i=len-2;i>=0;i--){
    
    
            r_max[i]=Math.max(r_max[i+1],height[i]);
        }

        //累加结果
        for (int i=1;i<len-1;i++){
    
    
            ans+=Math.min(l_max[i],r_max[i])-height[i];
        }
        return ans;
    }
}

双指针解法 时间n 空间1

用双指针边走边算,节省下空间复杂度。
很难,想不出来建议直接看我的解析

首先看下面的代码:

public int trap(int[] height) {
    
    
        if(height.length==0) return 0;
        int ans=0;
        int len=height.length;

        int l_max=0,r_max=0;

        int left=0,right=len-1;
        while (left<=right){
    
    
            l_max=Math.max(l_max,height[left]);
            r_max=Math.max(r_max,height[right]);
            left++; right--;
    }
}

对于这部分代码,请问l_max和r_max分别表示什么意义呢?

很容易理解,l_max是[0..left]中最高柱子的高度,r_max是[right..n-1]的最高柱子的高度。

明白了这一点,直接看解法:

class Solution {
    
    
    public int trap(int[] height) {
    
    
        if(height.length==0) return 0;
        int ans=0;
        int len=height.length;

        int l_max=0,r_max=0;

        int left=0,right=len-1;
        while (left<=right){
    
    
            l_max=Math.max(l_max,height[left]);
            r_max=Math.max(r_max,height[right]);

            if(l_max<r_max){
    
    
                ans+=(l_max-height[left]);
                left++;
            }else {
    
    
                ans+=(r_max-height[right]);
                right--;
            }
        }
        return ans;
    }
}

备忘录解法对比

之前的备忘录解法,l_max[i]和r_max[i]分别代表height[0..i]和height[i..n-1]的最高柱子高度。
在这里插入图片描述
但是双指针解法中,l_max和r_max代表的是height[0..left]和height[right..n-1]的最高柱子高度。

在这里插入图片描述
比如这段代码:

if (l_max < r_max) {
    
    
    res += l_max - height[left];
    left++; 
} 

此时的l_max是left指针左边的最高柱子,但是r_max并不一定是left指针右边最高的柱子,这真的可以得到正确答案吗?
可以,因为我们只在乎min(l_max, r_max)
对于上图的情况,我们已经知道l_max < r_max了,至于这个r_max是不是右边最大的,不重要。重要的是我已经知道了l_max < 右边最大是一定成立的就行。

猜你喜欢

转载自blog.csdn.net/qq_40337086/article/details/115861904