力扣第四十二题-接雨水

550 阅读2分钟

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

前言

力扣第四十二题 接雨水 如下所示:

给定 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

一、思路

这一题其实我刚开始是这么想的:如果需要接雨水必定会有一个 峰谷,即先减再增加。所以找到所有的 峰谷峰顶 就能准确计算出面积了。

实现代码如下所示(错误示范):

public int trap(int[] height) {
    if (height == null || height.length<3)
        return 0;
    int ret = 0;
    int len = height.length;
    // 只有出现低谷才可以容纳雨水
    boolean down = false;
    int left = 0;
    List<Integer> boundary = new ArrayList<>();
    // 从左到右找到所有的谷底
    for (int i=1; i<len; i++) {
        if (height[i] >= height[i-1]) {
            left = i ;
            down = false;
        } else {
            if (!down) {
                // 趋势为下降
                down = true;
                boundary.add(left);
                // 如果有两个元素了
                if (boundary.size() == 2) {
                    // 计算高度
                    ret += calc(height, boundary.get(0), boundary.get(1));
                    boundary.clear();
                    down = false;
                }
            }

        }
    }
    return ret;
}

public int calc(int[] height, int a, int b) {
    int ret = 0;
    int min = Math.min(height[a], height[b]);
    for (int i=a; i<=b; i++) {
        ret += Math.max(min - height[i], 0);
    }
    return ret;
}

但是我发现没办法处理下面这种情况:某一个 峰谷 对应的 左边峰顶 并不是紧挨着当前这个 峰谷的

image.png

所以解这道题的关键就是要找到:每个下标 i 左边的最高点和右边的最高点

总体的步骤分为一下两步:

  1. 找到所有下标 i 对应的 左最高点右最高点
  2. 遍历计算面积和:下标 i 对应的面积为 较低的高点减去当前的高度(类似于木桶效应,取决于短板),公式为 Math.min(leftMax[i], rightMax[i]) - height[i]

找到最高点的过程可以见下面的例子

举个例子(寻找最高点)

变量说明
leftMax[]:左最高点数组

此处以寻找 左最高点 作为例子,右最高点 步骤相同,故省略

下标 i 对应 左最高点leftMax[i-1]height[i] 中的 较大值,即 leftMax[i] = Math.max(leftMax[i-1], height[i])

所以我们只需要 从左至右 遍历数组,就可以初始化完成 左最高点数组

tips:上面这个过程就是简单的 动态规划(通过状态转移方程,自底向上求解)

二、实现

实现代码

实现代码与思路中保持一致

public int trap(int[] height) {
    if (height == null || height.length<3)
        return 0;
    int ret = 0;
    int len = height.length;
    int[] leftMax = new int[len];   // i下标及其左边最高的元素
    int[] rightMax = new int[len];  // i下标及其右边最高的元素
    // 初始化leftMax
    leftMax[0] = height[0];
    for (int i=1; i<len; i++) {
        leftMax[i] = Math.max(leftMax[i-1], height[i]);
    }
    // 初始化rightMax
    rightMax[len -1] = height[len - 1];
    for (int i=len-2; i>-1; i--) {
        rightMax[i] = Math.max(rightMax[i+1], height[i]);
    }
    // 计算面积
    for (int i=0; i<len; i++) {
        ret += Math.min(leftMax[i], rightMax[i]) - height[i];
    }
    return ret;
}

测试代码

public static void main(String[] args) {
    int[] nums = {4,2,0,3,2,5};
    new Number42().trap(nums);
}

结果

image.png

三、总结

感谢看到最后,非常荣幸能够帮助到你~♥