leetcode 42 ——接雨水问题

接雨水

注意和盛最多水的容器区别。

盛最多水的容器
在这里插入图片描述
接雨水
在这里插入图片描述

1、题目

原题链接
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
在这里插入图片描述
上面是由数组 [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、思路

(1)明确对于位置 i,能装下多少水呢?
根据木桶原理(三维层次,这里简化成二维平面),受制于左右两侧木板的高度,并且取决于高度较低的一侧,这很好理解。即:

  • 位置 i 能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关
  • 两个柱子高度为 l_max 和 r_max位置 i 最大的水柱高度就是 min(l_max, r_max)
water[i] = min(
               # 左边最高的柱子
               max(height[0..i]),  
               # 右边最高的柱子
               max(height[i..end]) 
            ) - height[i]

在这里插入图片描述

3、题解

3.1、暴力解法

int trap(vector<int>& height) {
    int n = height.size();
    int ans = 0;
    for (int i = 1; i < n - 1; i++) {
        int l_max = 0, r_max = 0;
        // 找右边最高的柱子
        for (int j = i; j < n; j++)
            r_max = max(r_max, height[j]);
        // 找左边最高的柱子
        for (int j = i; j >= 0; j--)
            l_max = max(l_max, height[j]);
        // 如果自己就是最高的话,
        // l_max == r_max == height[i]
        ans += min(l_max, r_max) - height[i];
    }
    return ans;
}

时间复杂度 O(N^2),空间复杂度 O(1)

3.2、空间换时间(备忘录优化)

之前的暴力解法,不是在每个位置 i 都要计算 r_max 和 l_max 吗?

我们开两个数组 r_max 和 l_max 充当备忘录,l_max[i] 表示位置 i 左边最高的柱子高度r_max[i] 表示位置 i 右边最高的柱子高度。预先把这两个数组计算好,避免重复计算

int trap(vector<int>& height) {
    if (height.empty()) return 0;
    int n = height.size();
    int ans = 0;
    // 数组充当备忘录
    vector<int> l_max(n), r_max(n);
    // 初始化 base case
    l_max[0] = height[0];
    r_max[n - 1] = height[n - 1];
    // 从左向右计算 l_max
    for (int i = 1; i < n; i++)
        l_max[i] = max(height[i], l_max[i - 1]);
    // 从右向左计算 r_max
    for (int i = n - 2; i >= 0; i--) 
        r_max[i] = max(height[i], r_max[i + 1]);
    // 计算答案
    for (int i = 1; i < n - 1; i++) 
        ans += min(l_max[i], r_max[i]) - height[i];
    return ans;
}

时间复杂度降低为 O(N),已经是最优了,但是空间复杂度是 O(N)

3.3、双指针解法

之前的暴力解法备忘录解法,原理如下:
在这里插入图片描述
双指针解法中,l_max 和 r_max 代表的是 height[0..left]height[right..end] 的最高柱子高度。
在这里插入图片描述

//l_area左侧高度记录,对r_area 同理
if (l_area  < r_area ) {
    ans += l_area  - height[left];
    left++; 
}

此时的 l_max 是 left 指针左边的最高柱子,但是 r_max 并不一定是 left 指针右边最高的柱子这真的可以得到正确答案吗?

其实这个问题要这么思考,我们只在乎 min(l_max, r_max)。对于上图的情况,我们已经知道 l_area < r_area 了,至于这个 r_max 是不是右边最大的,不重要,重要的是 height[i] 能够装的水只和 l_max 有关

l_max < r_max,解读为,右边已经有一个靠山给我们撑着了,并且这个靠山比左侧的靠山还要靠谱,我们就别纠结右侧靠山是不是有更靠谱的了(更高的),并且你要清晰的看到,左侧l_area = max(l_area,height[left]); l_area 一直是左侧最高的,所以更不要担心左侧会漏水

class Solution {
public:
    int trap(vector<int>& height) {
        int len = height.size();
        if(len < 3)//最少三块才能有聚水的可能
            return 0;
        int l_area = height[0],r_area = height[len - 1];
        int ans = 0;
        int left = 0,right = len - 1;
        while(left <= right)
        {
            l_area = max(l_area,height[left]);
            r_area = max(r_area,height[right]);

            if(l_area < r_area)
            {
                ans += l_area - height[left];
                left++;
            }
            else
            {
                ans += r_area - height[right];
                right--;
            }
        }
        return ans;
    }
};

参考

https://labuladong.gitbook.io/algo/gao-pin-mian-shi-xi-lie/jie-yu-shui

猜你喜欢

转载自blog.csdn.net/JMW1407/article/details/107709979