这里我们可以用两种方法去解决巧妙地解决这个题。首先来看一下题目
题目描述
给定 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)
我们的目标就是求出++所有位置的蓄水量的值再相加++。
方法一:动态规划
此方法通过预计算,直观地实现了上述核心思想。
思路
- 预计算左侧最大高度:创建一个数组 left_max_arr,长度与 height 相同。从左到右遍历 height 数组,对于每个位置 i,left_max_arr[i] 存储从索引 0 到 i 的所有柱子中的最大高度。
- 预计算右侧最大高度:创建另一个数组 right_max_arr。这一次,我们从右到左遍历 height 数组,对于每个位置 i,right_max_arr[i] 存储从索引 i 到 n-1 的所有柱子中的最大高度。
- 计算总雨水量:当我们拥有了每个位置的左侧最高和右侧最高之后,就可以进行第三次遍历。对于每个位置 i,其水位就是 min(left_max_arr[i], right_max_arr[i])。用这个水位减去当前柱子的高度 height[i],就是该位置的蓄水量。将所有位置的蓄水量累加起来,即为最终答案。

复杂度分析
- 时间复杂度: 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;
}
};
