力扣 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 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

题解1:按行来求,一行一行算,先计算出最高的“墙”的高度,然后从最高的那行开始遍历,对于每一行来说,比如第5行(首行是第1行),从左到右遍历一行,如果现在该行某一位置墙的高度大于等于5,我们就记录下来,假设用 l 记录,继续向后遍历,发现又出现一个墙的高度大于等于5的位置,用  r  记录下来,那么我们现在就可以确定,l 和 r之间肯定是低洼,可以蓄水的地方,并且由于我们只关心这一行,因此,蓄水的容量就是 r-l+1 。继续遍历这一行的时候,需要先将左边界 l 重置成 r,即 l  =  r,下一个低洼,就以该 l 作为新的左边界,同样的方法寻找右边界。

处理完一行之后,行数减1开始处理下一行。

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

        int max = Arrays.stream(height).max().orElseThrow(() -> new IllegalArgumentException());
        int count = 0;

        for (int h = max; h > 0; h--) {
            int l = -1;
            int r = 0;
            while (r < len) {
                if (height[r] >= h) {
                    if (l != -1) {
                        count += r - l - 1;
                    }
                    l = r;
                }
                r++;
            }
        }

        return count;
    }
}

代码解释:l 是否等于-1是用来判断现在是否已经找到了一个左边界了,寻找左边界的过程是用 r 的递增来实现的,r 找到一个边界,就会通知 l ,如果是该行第一个左边界,那么 l 是等于-1的,直接将l = r,r 继续寻找下一个边界,再次找到,又来通知 l ,但是现在发现 l 不等于 -1,说明现在找出来一对左右边界了,可以进行蓄水量计算了,计算完成之后,再将 l 置为 r,指定下一个低洼的左边界,以此类推。

说明:320/322 还是会超时,暴力解法可以不用,但是得会,其实我自己做出来的暴力解法要比这个麻烦,但是整体思路是一致的,贴在这里,留个念想,毕竟是第一次这么接近hard题的全对:

public static int trap(int[] height) {
        //首先找到最高的元素,就是循环进行次数
        int max = Arrays.stream(height).max().orElseThrow(() -> new IllegalArgumentException());
        int len = height.length;
        int count = 0;
        while (max > 0) {
            max--;
            //int arr[] = new int[len];
            List<Integer> list = new ArrayList<>();
            //从上往下
            for (int i = 0; i < len; i++) {
                /*list.add((height[i] - max + 1) > 0:1 ? 0);*/
                if((height[i] - max )>0){
                    list.add(1);
                }else{
                    list.add(0);
                }

            }
           // System.out.println("第"+max+"次循环: "+list);
            //初始化l,r
            int l = list.indexOf(1);
            int r = l+1;
            //System.out.println("初始化的l r"+l+" "+r);
            while(r<len){

                while(r<len&&list.get(r)==0)
                {
                    r++;
                }
               // System.out.println("不 "+l+" "+r);

                if(r<len) {
                    count += (r - l - 1);
                    //System.out.println("count: "+count);
                    l = r;
                    r = l + 1;
                }
            }

        }
        return count;

    }

题解2:按列求,没有按行求直观,还需要分成三种其概况讨论。涉及三个墙,当前在求的这列墙,这列墙左边最高和右边最高的墙,当往左右最高的墙中间添水的时候,当列在求的墙能蓄多少水,取决于当列以左最高的墙 和 当列以右最高的墙中较矮的一堵(木桶效应),用较矮的一堵墙减去当前在求列墙的高度就是当前列能够蓄水量。这三堵墙的关系又分成以下三种情况:

左右最高的墙中  较矮的那一堵墙高度 大于正在求的列

计算:

 

左右最高的墙中 较矮的那一堵墙高度  小于当前在求列的高度

计算:当前列没有蓄水

左右最高的墙中 较矮的那一堵墙高度 等于当前在求列的高度

计算:当前列不会蓄水

总结:只有当前列比左右最大高度中较小的那堵墙矮的时候才会蓄水。

代码实现:

public int trap(int[] heights) {
    int totalWater = 0;
    // 最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2
    for (int current = 1; current < heights.length - 1; current++) {
        int leftMax = 0;
        // 找出左边最高
        for (int left = current - 1; left >= 0; left--) {
            if (heights[left] > leftMax) {
                leftMax = heights[left];
            }
        }
        int rightMax = 0;
        // 找出右边最高
        for (int right = current + 1; right < heights.length; right++) {
            if (heights[right] > rightMax) {
                rightMax = heights[right];
            }
        }
        // 找出两端较小的
        int minHeight = Math.min(leftMax, rightMax);
        // 只有较小的一段大于当前列的高度才会有水,其他情况不会有水
        if (minHeight > heights[current]) {
            totalWater += minHeight - heights[current];
        }
    }
    return totalWater;
}

题解3:针对上述按照列求,会发现有很多重复计算,主要是在,每次计算一列墙的时候,都需要去计算其左边和右边最高墙的高度,这样实际上每计算一列墙都需要循环遍历一遍整个数组,时间复杂度很大,因此考虑用一种算法实现计算每列墙左边和右边的最高墙的高度,并且通过一边循环就计算出来,用数组max_left(i)表示的就是i列以左墙的最高值,max_right(i)就是i列以右墙的最高值,考虑使用dp ,dp方程:

 max_left[i] = Math.max(max_left[i - 1], height[i - 1]);
 max_right[i] = Math.max(max_right[i + 1], height[i + 1]);

完整的代码实现:

public int trap(int[] heights) {
    int totalWater = 0;
    int n = heights.length;
    
    // maxLeft[i] 表示位置 i 左侧(不包括 i)的最大高度
    int[] maxLeft = new int[n];
    // maxRight[i] 表示位置 i 右侧(不包括 i)的最大高度
    int[] maxRight = new int[n];
    
    // 计算 maxLeft 数组
    for (int i = 1; i < n - 1; i++) {
        maxLeft[i] = Math.max(maxLeft[i - 1], heights[i - 1]);
    }
    
    // 计算 maxRight 数组
    for (int i = n - 2; i >= 0; i--) {
        maxRight[i] = Math.max(maxRight[i + 1], heights[i + 1]);
    }
    
    // 计算可以存储的水量
    for (int i = 1; i < n - 1; i++) {
        int minHeight = Math.min(maxLeft[i], maxRight[i]);
        if (minHeight > heights[i]) {
            totalWater += minHeight - heights[i];
        }
    }
    
    return totalWater;
}

  • 28
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值