力扣42. 接雨水-java多种实现方法

题目

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

在这里插入图片描述
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6

解法一

暴力法

思路:按列来求水,循环遍历每列,每一次只看当前列能否装水,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。
能够装下多少水,只需要看左边最高的墙和右边最高的墙中较矮的一个。

此处借用某位大佬的图来分析,原文链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-8/
当前列能否装水,有以下三种情况:

1. 较矮的墙高度大于当前所求列的墙的高度
在这里插入图片描述
简化一下,我们把无关列去掉,能够看的更清楚。
在这里插入图片描述
从图中可以看出,正在求的列与左边较矮的墙相减即为当前列的注水量。(注:我们只关注当前所求列与两边中最矮的列的高度差)

2. 较矮的墙的高度小于当前所求列的墙的高度
在这里插入图片描述
把图片简化一下
在这里插入图片描述
从图中可以看出,正在求的列大于最矮的墙,此时注水会从左侧流失,故无法装水。(注:我们只关注当前所求列与两边中最矮的列的高度差)

3. 较矮的墙等于当前列的高度
此时和上一种情况一样,无法装水
在这里插入图片描述
java代码:

public int trap(int[] height) {
    int sum = 0;
    //最两端的列不用考虑,因为一定不会有水。下标从 1 到 length - 2
    //for循环遍历每一列,每次遍历都要从新找当前列与左右两边的最高列,进行比较,选择最小的那列与当前列比较,根据三种情况判断是否可以注水。
    for (int i = 1; i < height.length - 1; i++) {
        int max_left = 0;
        //找出左边最高列
        for (int j = i - 1; j >= 0; j--) {
            if (height[j] > max_left) {
                max_left = height[j];
            }
        }
        int max_right = 0;
        //找出右边最高列
        for (int j = i + 1; j < height.length; j++) {
            if (height[j] > max_right) {
                max_right = height[j];
            }
        }
        //比较两列中最小的列
        int min = Math.min(max_left, max_right);
        //只有较小的一段大于当前列的高度才会有水,其他情况不会有水
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

解法二

动态规划

思路:
对于暴力法中,它找左右墙的高度每次都从新遍历,时间复杂度极高。在这里,我们可以用两个数组采用动态规划的方式来求。
第一步:
先定义两个数组,将每列的左右墙的最大高度求出来,存在数组中。
第二步:
在用一次for循环,对所求列进行比较高度即可

java代码:

public int trap(int[] height) {
    int sum = 0;
    //定义两个数组存放每列的墙的最大高度
    int[] left_max = new int[height.length];
    int[] right_max = new int[height.length];
    
    //循环左侧的每列墙的左侧最大高度
    for (int i = 1; i < height.length - 1; i++) {
        left_max[i] = Math.max( left_max[i - 1], height[i - 1]);
    }
    //循环左侧的每列墙的左侧最大高度
    for (int i = height.length - 1; i >= 0; i--) {
        right_max[i] = Math.max(right_max[i + 1], height[i + 1]);
    }
    //循环所求的每列
    for (int i = 1; i < height.length - 1; i++) {
        int min = Math.min(left_max[i], right_max[i]);
        if (min > height[i]) {
            sum = sum + (min - height[i]);
        }
    }
    return sum;
}

解法三

双指针

思路:
和解法 2 相比,我们不从左和从右分开计算,我们想办法一次完成遍历。
从动态编程方法的示意图中我们注意到,只要 right_max[i]>left_max[i] (元素 0 到元素 6),积水高度将由 left_max 决定,类似地left_max[i]>right_max[i](元素 8 到元素 11)。
所以我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。
我们必须在遍历时维护left_max 和 right_max ,但是我们现在可以使用两个指针交替进行,实现 1 次遍历即可完成。

此处参考自:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode/

算法:

初始化left 指针为 0 并且 right 指针为 size-1
While left<right,
do:
If height[left] < height[right]
If height[left]≥left_max, 更新 left_max
Else 累加 left_max−height[left] 到 ans
left = left + 1.
Else
If height[right]≥right_max, 更新right_max
Else 累加 right_max−height[right] 到 ans
right = right - 1.

java代码

public int trap(int[] height) {
    int left = 0,right = height.length-1;
    int ret = 0;
    int left_max = 0,right_max = 0;
    while (left < right){
        if (height[left] < height[right]){
            if (height[left]>left_max){
                left_max = height[left];
            }else{
                ret += left_max - height[left];
            }
            left++;
        }else{
            if(height[right] >= right_max){
                right_max = height[right];
            }else{
                ret += right_max - height[right];
            }
            right--;
        }
    }
    return ret;
}

此方法的执行效率:

以上为博主每日一题后的思路汇总,其中参考了不少大佬的精华。如有错误之处,欢迎指正.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章