LeetCode Top 100 Liked Questions 42. Trapping Rain Water (Java版; Hard)

welcome to my blog

LeetCode Top 100 Liked Questions 42. Trapping Rain Water (Java版; Hard)

题目描述
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.


The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!

Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

Example:

class Solution {
    public int trap(int[] height) {
        int n = height.length;
        if(n<=2){
            return 0;
        }
        int Lmax=height[0], Rmax=height[n-1];
        int left = 1, right = n-2;
        int res=0;
        //注意理解这个循环条件
        while(left<=right){
            if(Lmax<Rmax){
                if(Lmax-height[left]>0){
                    res += Lmax-height[left];
                }
                Lmax = Math.max(Lmax, height[left]);
                left++;
            }else{
                if(Rmax-height[right]>0){
                    res += Rmax-height[right];
                }
                Rmax = Math.max(Rmax, height[right]);
                right--;
            }
        }
        return res;
    }
}
第二次做; 除了相向而行的双指针外, 还得用两个变量记录双指针外侧的最大值; 理解height[leftMaxIndex] == height[rightMaxIndex]时,从哪边遍历都行, 比如看看例子[3,0,2,2,3]
/*
双指针, 将N^2的搜索空间降低为N
舍弃高柱子中更矮的柱子
*/
class Solution {
    public int trap(int[] height) {
        int n = height.length;
        //双指针, 从两端向中间
        int left=0, right=n-1;
        //leftMaxIndex表示[0,left]范围上最大值对应的索引;
        //rightMaxIndex表示[right,n-1]范围上最大值对应的索引
        int leftMaxIndex=0, rightMaxIndex=n-1;
        int res = 0;
        while(left<=right){
            int leftMax = height[leftMaxIndex];
            int rightMax = height[rightMaxIndex];
            //左边的高柱子更矮, 移动左指针
            if(leftMax < rightMax){
                //只需要保证leftMax>height[left]就能存水, 因为右边的高柱子更高
                if(leftMax > height[left]){
                    res += leftMax - height[left];
                }
                //不能存水的话说明当前柱子比左侧的高柱子更高, 更新左侧高柱子的索引
                else{
                    leftMaxIndex = left;
                }
                //update
                left++;
            }
            //右边的高柱子更矮, 或者右边的高柱子和左边的高柱子一样高, 此时移动谁都可以
            else{
                if(rightMax > height[right]){
                    res += rightMax - height[right];
                }
                else{
                    rightMaxIndex = right;
                }
                right--;
            }
        }
        return res;
    }
}
第二次做; 一列一列地计算, 先用哈希表记录每一列左右两侧的最大值
//一列一列地计算; 计算之前先统计每一列左右两边的最大值; 只有当前列的高度小于它左右两侧最大值中的较小的那个才能存住水
class Solution {
    public int trap(int[] height) {
        //保存每个数左侧的最大值
        HashMap<Integer, Integer> leftMax = new HashMap<>();
        //保存每个数右侧的最大值
        HashMap<Integer, Integer> rightMax = new HashMap<>();
        int n = height.length;
        // leftMax.put(0,0);
        int max = 0;
        for(int i=1; i<n; i++){
            if(height[i-1] > max)
                max = height[i-1];
            leftMax.put(i, max);
        }
        
        // rightMax.put(n-1, 0);
        max = 0;
        for(int i=n-2; i>=0; i--){
            if(height[i+1]>max)
                max = height[i+1];
            rightMax.put(i, max);
        }
        int res = 0;
        for(int i=1; i<n-1; i++){
            int left = leftMax.get(i), right = rightMax.get(i);
            //别忘记减去当前高度
            res += Math.min(left, right)>height[i]?Math.min(left, right)-height[i]:0;
        }
        return res;  
    }
}
第一次做, 双指针, 2ms, 在遍历每一根柱子时, 可能从左往右遍历, 也可能从右往左遍历, 举例来说, 如果左墙比右墙低, 就从左往右遍历, 因为此时柱子的存水量最多是左墙的高度; 这道题主要就是理解什么时候从左往右遍历, 什么时候从右往左遍历; 还需要两个变量分别存储左墙和右墙的索引, 注意如何更新墙的索引
/*
得理解height[leftMaxIndex] == height[rightMaxIndex]时,从哪边遍历都行, 例子[3,0,2,2,3]
*/
class Solution {
    public int trap(int[] height) {
        if(height==null || height.length < 3)
            return 0;
        //用两个变量分别存储左侧的最大值索引和右侧的最大值索引
        int leftMaxIndex=0, rightMaxIndex=height.length-1;
        //逐列判断
        int sum = 0;
        //双指针
        int left = 1, right = height.length-2;
        //while循环: 要遍历完每一根柱子
        while(left <= right){
            //height[leftMaxIndex] == height[rightMaxIndex]时,从哪边遍历都行
            //右边的墙更高, 则从左向右遍历
            if(height[leftMaxIndex] < height[rightMaxIndex]){
                //此时, 如果左墙比当前柱子高, 就能存住水
                if(height[leftMaxIndex] > height[left]){
                    sum += height[leftMaxIndex] - height[left];
                }
                //否则说明左墙比当前柱子低或者和当前柱子一样高, 更新左墙索引
                else{
                    leftMaxIndex = left;
                }
                //update
                left++;
            }
            //左边的墙更高或者两堵墙一样高, 则从右向左遍历
            else{
                //此时, 如果右墙比当前柱子高, 就能存住水
                if(height[rightMaxIndex] > height[right]){
                    sum += height[rightMaxIndex] - height[right];
                }
                //否则说明右墙比当前柱子低或者和当前柱子一样高, 更新右墙索引
                else{
                    rightMaxIndex = right;
                }
                //update
                right--;
            }
        }
        return sum;
    }
}
第一次做,拿空间换时间, 时间复杂度O(N),3ms, 空间复杂度O(N); 一列一列地处理, 不再重新求左右的最大值, 而是保存左右的最大值, 适当地进行更新; 左侧最大值好办, 主要是如何更新右侧最大值? 从右向左遍历! 别老是想着一遍完成任务, 其实两遍遍历,三遍遍历都是可以的, 没有增加时间复杂度的量级
class Solution {
    public int trap(int[] height) {
        if(height==null || height.length<3)
            return 0;
        
        int sum=0;
        //拿空间换时间
        int[] leftMax = new int[height.length];
        int[] rightMax = new int[height.length];
        //先求leftMax的各个值, leftMax[i]表示0,...,i对应的最大值
        leftMax[0] = height[0];
        for(int i=1; i<height.length; i++){
            leftMax[i] = Math.max(height[i], leftMax[i-1]);//竟然有点绕... 容易写错
        }
        //求rightMax的各个值, rightMax[i]表示i,...height.length-1对应的最大值
        rightMax[height.length-1] = height[height.length-1];
        for(int i=height.length-2; i>=0; i--){
            rightMax[i] = Math.max(height[i], rightMax[i+1]);
        }
        //开始逐列处理,第0列和第height.length-1列不能存水
        for(int i=1; i<height.length-1; i++){
            if(leftMax[i-1]>height[i] && rightMax[i+1]>height[i])
                sum = sum + Math.min(leftMax[i-1], rightMax[i+1]) - height[i];
        }
        return sum;
    }
}
第一次做, 时间复杂度O(N^2), 95ms, 空间复杂度O(1), 一列一列地计算; 对于当前列来说, 只有左侧的最大值和右侧的最大值都大于当前值时可能存水; 这道题耗时的地方在于,考虑每一列时都要重新计算其左右的最大值, 如果能把左右的最大值保存下来就好了!
/*
先递减再递增才能存住水
先写个O(N^2)的解法吧
*/
class Solution {
    public int trap(int[] height) {
        if(height==null || height.length<3)
            return 0;
        
        int leftMax=0, rightMax=0, sum=0;
        for(int i=1; i<height.length-1; i++){
            //找到左边的最大值
            leftMax = getMax(height, 0, i-1);
            //如果左侧最大值比当前值小或者相等,那么当前列不能存住水, 进入下一轮循环
            if(leftMax<=height[i])
                continue;
            //找到右侧的最大值
            rightMax = getMax(height, i+1, height.length-1);
            //如果右侧的最大值比当前值小或者相等, 那么当前列不能存水, 进入下一轮循环
            if(rightMax<=height[i])
                continue;
            //
            sum = sum + Math.min(leftMax, rightMax) - height[i];
        }
        return sum;
    }
    public int getMax(int[]arr, int i, int j){
        int max = 0;
        for(int k=i; k<=j; k++)
            max = Math.max(max, arr[k]);
        return max;
    }
}
题解, 双指针, 不再用数组存储最大值, 只用了两个常量
public int trap(int[] height) {
    int sum = 0;
    int max_left = 0;
    int max_right = 0;
    int left = 1;
    int right = height.length - 2; // 加右指针进去
    for (int i = 1; i < height.length - 1; i++) {
        //从左到右更
        if (height[left - 1] < height[right + 1]) {
            max_left = Math.max(max_left, height[left - 1]);
            int min = max_left;
            if (min > height[left]) {
                sum = sum + (min - height[left]);
            }
            left++;
        //从右到左更
        } else {
            max_right = Math.max(max_right, height[right + 1]);
            int min = max_right;
            if (min > height[right]) {
                sum = sum + (min - height[right]);
            }
            right--;
        }
    }
    return sum;
}
LeetCode优秀例子(https://leetcode.com/problems/trapping-rain-water/discuss/17391/Share-my-short-solution.)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值