Java轻松搞定leetcode前100系列之42. 接雨水 详细通俗的思路分析,多解法

题目描述:

给定 n 个非负整数表示每个宽度为 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

提示:

  • n == height.length
  • 0 <= n <= 3 * 104
  • 0 <= height[i] <= 105

思路:

黑色的看成墙,蓝色的看成水,宽度一样,给定一个数组,每个数代表从左到右墙的高度,求出能装多少单位的水。也就是图中蓝色正方形的个数。

解法一:按行求

就是先求高度为 1 的水,再求高度为 2 的水,再求高度为 3 的水。

整个思路就是,求第 i 层的水,遍历每个位置,如果当前的高度小于 i,并且两边有高度大于等于 i 的,说明这个地方一定有水,水就可以加 1。

如果求高度为 i 的水,首先用一个变量 temp 保存当前累积的水,初始化为 0。从左到右遍历墙的高度,遇到高度大于等于 i 的时候,开始更新 temp。更新原则是遇到高度小于 i 的就把 temp 加 1,遇到高度大于等于 i 的,就把 temp 加到最终的答案 ans 里,并且 temp 置零,然后继续循环。

我们就以题目的例子讲一下。

先求第 1 行的水。

也就是红色区域中的水,数组是 height = [ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 ] 。

原则是高度小于 1,temp ++,高度大于等于 1,ans = ans + temp,temp = 0。

temp 初始化为 0,ans = 0

height[0] 等于 0 < 1,不更新。

height[1] 等于 1 >= 1,开始更新 temp。

height[2] 等于 0 < 1,temp = temp + 1 = 1。

height[3] 等于 2 >= 1,ans = ans + temp = 1,temp = 0。

height[4] 等于 1 >= 1,ans = ans + temp = 1,temp = 0。

height[5] 等于 0 < 1,temp = temp + 1 = 1。

height[6] 等于 1 >= 1,ans = ans + temp = 2,temp = 0。

剩下的 height[7] 到最后,高度都大于等于 1,更新 ans = ans + temp = 2,temp = 0。而其实 temp 一直都是 0,所以 ans 没有变化。

再求第 2 行的水。

也就是红色区域中的水,数组是 height = [ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 ]。

原则是高度小于 2,temp ++,高度大于等于 2,ans = ans + temp,temp = 0。

temp 初始化为 0,ans 此时等于 2。

height[0] 等于 0 < 2,不更新。

height[1] 等于 1 < 2,不更新。

height[2] 等于 0 < 2,不更新。

height[3] 等于 2 >= 2,开始更新

height[4] 等于 1 < 2,temp = temp + 1 = 1。

height[5] 等于 0 < 2,temp = temp + 1 = 2。

height[6] 等于 1 < 2,temp = temp + 1 = 3。

height[7] 等于 3 >= 2,ans = ans + temp = 5,temp = 0。

height[8] 等于 2 >= 2,ans = ans + temp = 3,temp = 0。

height[9] 等于 1 < 2,temp = temp + 1 = 1。

height[10] 等于 2 >= 2,ans = ans + temp = 6,temp = 0。

height[11] 等于 1 < 2,temp = temp + 1 = 1。

然后结束循环,此时的 ans 就是6。

再看第 3 层。

按照之前的算法,之前的都是小于 3 的,不更新 temp,然后到 height[7] 等于 3,开始更新 temp,但是后边没有 height 大于等于 3 了,所以 ans 没有更新。

所以最终的 ans 就是 6。

代码如下,先说好,这种方法AC不过!!!只是一种思路,如果给出的柱子高度都很大就gg!!!

public int trap(int[] height) {
        int sum = 0;
        int max = getMaxHigh(height);//找到最大的高度,以便遍历
        for (int i = 1; i <= max; i++) {
            int temp = 0;
            boolean isStart = false; //标记是否已经开始更新 temp
            for (int j = 0; j < height.length; j++) {
                if (isStart && height[j] < i) {
                    temp++;
                }
                if (height[j] >= i) {
                    sum = sum + temp;
                    temp = 0;
                    isStart = true;
                }
            }
        }
        return sum;
    }

    private int getMaxHigh(int[] height) {
        int max = 0;
        for (int i = 0; i < height.length; i++) {
            max = max > height[i] ? max : height[i];
        }
        return max;
    }

时间复杂度:如果最大的数是 mm,个数是 nn,那么就是 O(m∗n)。

空间复杂度:O(1)。

解法二:按列求

求每一列的水,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。

装水的多少,当然根据木桶效应,我们只需要看左边最高的墙和右边最高的墙中较矮的一个就够了。

所以,根据较矮的那个墙和当前列的墙的高度可以分为三种情况。

  • 较矮的墙的高度大于当前列的墙的高度

把正在求的列左边最高的墙和右边最高的墙确定后,然后为了方便理解,我们把无关的墙去掉。

这样就很清楚了,现在想象一下,往两边最高的墙之间注水。正在求的列会有多少水?

很明显,较矮的一边,也就是左边的墙的高度,减去当前列的高度就可以了,也就是 2 - 1 = 1,可以存一个单位的水。

  • 较矮的墙的高度小于当前列的墙的高度

同样的,我们把其他无关的列去掉。

想象下,往两边最高的墙之间注水。正在求的列会有多少水?

正在求的列不会有水,因为它大于了两边较矮的墙。

较矮的墙的高度等于当前列的墙的高度。

和上一种情况是一样的,不会有水。

明白了这三种情况,程序就很好写了,遍历每一列,然后分别求出这一列两边最高的墙。找出较矮的一端,和当前列的高度比较,结果就是上边的三种情况。 

public int trap(int[] height) {
        int sum = 0;
        //左右两边的墙肯定不能装水,所以从1开始到length-1结束
        for (int i = 1; i < height.length - 1; i++) {
            int tmp = height[i];
            int left_max = 0;
            int right_max = 0;
            //找当前列左边最高的墙
            for (int j = i - 1; j >= 0; j--) {
                left_max = left_max > height[j] ? left_max : height[j];
            }
            //找当前列最右边的墙
            for (int k = i + 1; k < height.length; k++) {
                right_max = right_max > height[k] ? right_max : height[k];
            }
            //选取但前列左边最高的墙和右边最高的墙中,最矮的墙
            int min = Math.min(left_max, right_max);
            //只有最矮的墙大于当前列的时候,当前列才有水,= min - tmp
            if (min > tmp) {
                sum = sum + (min - tmp);
            }
        }
        return sum;
    }

时间复杂度:O(n²),遍历每一列需要 n,找出左边最高和右边最高的墙加起来刚好又是一个 n,所以是 n²。

空间复杂度:O(1)。

执行结果如下:

解法三: 动态规划

我们注意到,解法二中。对于每一列,我们求它左边最高的墙和右边最高的墙,都是重新遍历一遍所有高度,这里我们可以优化一下。

首先用两个数组,max_left [i] 代表第 i 列左边最高的墙的高度,max_right[i] 代表第 i 列右边最高的墙的高度。(一定要注意下,第 i 列左(右)边最高的墙,是不包括自身的)

对于 max_left我们其实可以这样求。

max_left [i] = Max(max_left [i-1],height[i-1])。它前边的墙的左边的最高高度和它前边的墙的高度选一个较大的,就是当前列左边最高的墙了。

对于 max_right我们可以这样求。

max_right[i] = Max(max_right[i+1],height[i+1]) 。它后边的墙的右边的最高高度和它后边的墙的高度选一个较大的,就是当前列右边最高的墙了。

这样,我们再利用解法二的算法,就不用在 for 循环里每次重新遍历一次求 max_left 和 max_right 了。

代码如下:

    public int trap(int[] height) {
        int sum = 0;
        int[] left_max = new int[height.length];
        int[] right_max = new int[height.length];
        //找第i列左边最大的,从第1列开始找
        for (int i = 1; i < height.length - 1; i++) {
            left_max[i] = Math.max(left_max[i - 1], height[i - 1]);
        }
        for (int j = height.length - 2; j > 0; j--) {
            right_max[j] = Math.max(right_max[j + 1], height[j + 1]);
        }
        for (int k = 1; k < height.length - 1; k++) {
            int min = Math.min(left_max[k], right_max[k]);
            if (min > height[k]) {
                sum = sum + (min - height[k]);
            }
        }
        return sum;
    }

时间复杂度:O(n)。

空间复杂度:O(n),用来保存每一列左边最高的墙和右边最高的墙。

解法四:双指针

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值