LeetCode 42. 接雨水 | C++ 动态规划与双指针双解法题解
📌 题目描述
题目级别:困难 (Hard)
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
- 示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
💡 解题思路:核心物理定律 (木桶效应)
接雨水问题的核心在于**"化整为零"。我们不要从整体去考虑能接多少水,而是把目光聚焦在每一根单独的柱子**上。
问:下标为 i 的这根柱子,它的正上方到底能积攒多少水?
答:根据"木桶效应",这取决于它左边所有柱子中的最高点 ,以及右边所有柱子中的最高点 。
具体公式为:
第 i 列的水量 = min(左边最高, 右边最高) - 当前柱子的高度
(如果算出来是负数,说明当前柱子比两边都高,存不了水,取 0 即可)
🚀 解法一:前后缀分解 / 动态规划 (空间 O(N))
既然每一列都需要知道"左边最高"和"右边最高",我们可以提前把它们算出来存进数组里。
- 从左往右遍历一次,生成
left_max数组,记录每个位置及其左边的最高高度。 - 从右往左遍历一次,生成
right_max数组,记录每个位置及其右边的最高高度。 - 最后再遍历一次原数组,利用上面的公式计算水量并累加。
💻 C++ 代码实现 (标准规范版)
cpp
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) return 0;
// 规范做法:使用 vector 代替变长数组
vector<int> left_max(n);
vector<int> right_max(n);
// 1. 预处理左侧最大值数组
left_max[0] = height[0];
for (int i = 1; i < n; i++) {
left_max[i] = max(left_max[i - 1], height[i]);
}
// 2. 预处理右侧最大值数组
right_max[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
right_max[i] = max(right_max[i + 1], height[i]);
}
// 3. 计算总积水量
int res = 0;
for (int i = 0; i < n; i++) {
res += min(left_max[i], right_max[i]) - height[i];
}
return res;
}
};
🏆 解法二:双指针 (时间 O(N),空间 O(1) 面试终极解)
解法一虽然快,但占用了额外的数组空间。能不能用 O(1)O(1)O(1) 的空间解决?答案是双指针。
我们在数组两端分别放置指针 left 和 right,同时用两个变量 l_max 和 r_max 来记录左右两边见过的最高高度。
既然决定水量的木板是短板,那么:
- 如果 lmax<rmaxl_{max} < r_{max}lmax<rmax,说明左边这块板子更短。虽然我们不知道 right 指针以右还有没有更高的板子,但水桶的高度已经被左边的 lmaxl_{max}lmax 锁死了。此时我们可以放心地结算 left 指针所在列的水量,并将 left 向右移。
- 反之,如果 lmax>=rmaxl_{max} >= r_{max}lmax>=rmax,说明右边是短板,高度被 rmaxr_{max}rmax 锁死。我们结算 right 指针所在列的水量,并将 right 向左移。
💻 进阶 C++ 代码实现
cpp
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) return 0;
int left = 0, right = n - 1;
int l_max = 0, r_max = 0;
int res = 0;
while (left < right) {
// 更新左右两端的历史最高点
l_max = max(l_max, height[left]);
r_max = max(r_max, height[right]);
// 谁是短板,谁就决定了当前列的水量,并向中间移动
if (l_max < r_max) {
// 左侧是短板,结算 left 处的水量
res += l_max - height[left];
left++;
} else {
// 右侧是短板,结算 right 处的水量
res += r_max - height[right];
right--;
}
}
return res;
}
};