Leetcode力扣解题记录--第42题 接雨水(动规和分治法)

题目链接:42. 接雨水 - 力扣(LeetCode)

这里我们可以用两种方法去解决巧妙地解决这个题。首先来看一下题目

题目描述

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

提示:

  • n == height.length

  • 1 <= n <= 2 * 104

  • 0 <= height[i] <= 105

题目作答

核心解题思想

解决此问题的根本在于,对于数组中的任意一个位置 i,其上方能够积蓄的雨水高度,取决于它左侧所有柱子中的最高者 (left_max) 和右侧所有柱子中的最高者 (right_max) 中较矮的那一个。这个较矮的高度决定了该位置的水位。

  • 该位置 i 的最终水位高度为:water_level = min( left_max, right_max)。
  • 该位置 i 本身能存储的雨水量为:water[i] = water_level - height[i]。(如果结果为负数,则该处蓄水量为0)

我们的目标就是求出++所有位置的蓄水量的值再相加++。


方法一:动态规划

此方法通过预计算,直观地实现了上述核心思想。

思路

  1. 预计算左侧最大高度:创建一个数组 left_max_arr,长度与 height 相同。从左到右遍历 height 数组,对于每个位置 i,left_max_arr[i] 存储从索引 0 到 i 的所有柱子中的最大高度。
  2. 预计算右侧最大高度:创建另一个数组 right_max_arr。这一次,我们从右到左遍历 height 数组,对于每个位置 i,right_max_arr[i] 存储从索引 i 到 n-1 的所有柱子中的最大高度。
  3. 计算总雨水量:当我们拥有了每个位置的左侧最高和右侧最高之后,就可以进行第三次遍历。对于每个位置 i,其水位就是 min(left_max_arr[i], right_max_arr[i])。用这个水位减去当前柱子的高度 height[i],就是该位置的蓄水量。将所有位置的蓄水量累加起来,即为最终答案。

图片来源:毒瘤面试题:接雨水_哔哩哔哩_bilibili

复杂度分析

  • 时间复杂度: O(n)。我们进行了三次独立的线性遍历,总时间复杂度为 O(n)+O(n)+O(n)=O(n)。
  • 空间复杂度: O(n)。我们使用了两个长度为 n 的额外数组 left_max_arr 和 right_max_arr 来存储中间计算结果。

代码如下

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if (n < 3) {
            return 0;
        }

        // 1. left_max_arr 数组,记录从左到右的最大值
        vector<int> left_max_arr(n);
        left_max_arr[0] = height[0];
        for (int i = 1; i < n; ++i) {
            left_max_arr[i] = max(left_max_arr[i - 1], height[i]);
        }

        // 2. right_max_arr 数组,记录从右到左的最大值
        vector<int> right_max_arr(n);
        right_max_arr[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; --i) {
            right_max_arr[i] = max(right_max_arr[i + 1], height[i]);
        }

        // 3. 遍历每个位置,计算并累加雨水
        int total_water = 0;
        for (int i = 0; i < n; ++i) {
            // 计算当前位置的水位
            int water_level = min(left_max_arr[i], right_max_arr[i]);
            // 累加当前位置的蓄水量
            total_water += water_level - height[i];
        }
        
        return total_water;
    }
};

**方法二:**分治法

此方法是动态规划解法的空间优化版本,它在一次遍历中就完成了所有计算,无需额外的存储数组。

思路

第一步:找到全局最高点

首先,我们需要一次遍历整个 height 数组,找到其中最高的柱子的高度 max_height 和其对应的索引 max_index。这个最高的柱子就像一座山峰,它自然地将整个地形分成了左、右两个部分。在它左边的区域,积水情况最多只会受到它本身以及它左边的墙的影响;同理,右边的区域也是如此。

第二步:处理左半部分(从数组开头到最高点)

现在,我们从数组的最左边(索引 0)开始,向右遍历直到最高点所在的索引 max_index。

  • 我们维护一个变量 left_max,用来记录从左边到当前位置为止遇到的最高墙体。
  • 对于这个区间的任何一根柱子 height[i],它右边的最高墙一定是我们第一步找到的全局最高点 max_height。
  • 因此,在该位置 i 的蓄水高度瓶颈,就完全取决于其左边的最高墙 left_max。因为 left_max 不可能超过 max_height。
  • 所以,在位置 i 的蓄水量就是 left_max - height[i]。
  • 我们从左向右遍历,不断更新 left_max 并累加每个位置的蓄水量。

第三步:处理右半部分(从数组末尾到最高点)

处理完左边,我们用完全对称的逻辑来处理右半部分。

  • 我们从数组的最右边(索引 n-1)开始,向左遍历直到最高点所在的索引 max_index。
  • 我们维护一个变量 right_max,用来记录从右边到当前位置为止遇到的最高墙体。
  • 对于这个区间的任何一根柱子 height[i],它左边的最高墙一定是全局最高点 max_height。
  • 因此,在该位置 i 的蓄水高度瓶颈,就完全取决于其右边的最高墙 right_max。
  • 我们在遍历过程中,不断更新 right_max 并累加每个位置的蓄水量 right_max - height[i]。

将第二步和第三步计算出的蓄水量相加,就得到了最终的总量。

复杂度分析

  • 时间复杂度: O(n)。
  • 空间复杂度: O(1)。
cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if (n < 3) {
            return 0;
        }
        // 1. 找到全局最高点
        int max_height = 0;
        int max_index = 0;
        for (int i = 0; i < n; ++i) {
            if (height[i] > max_height) {
                max_height = height[i];
                max_index = i;
            }
        }
        int total_water = 0;
        
        // 2. 处理左半部分 (从 0 到 max_index)
        int left_max = 0;
        for (int i = 0; i < max_index; ++i) {
            // 更新左侧遇到的最高墙
            left_max = max(left_max, height[i]);
            // 计算当前位置的蓄水量并累加
            total_water += left_max - height[i];
        }
        
        // 3. 处理右半部分 (从 n-1 到 max_index)
        int right_max = 0;
        for (int i = n - 1; i > max_index; --i) {
            right_max = max(right_max, height[i]);
            total_water += right_max - height[i];
        }
        
        return total_water;
    }
};