🌧️ 问题描述
给定一个非负整数数组 height,每个元素代表一个宽度为 1 的柱子的高度。下雨后,柱子之间会形成凹槽,从而接住雨水。
目标:计算总共能接多少单位的雨水。
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
🧠 核心思想:一个位置能接多少水,取决于它左边和右边“最高墙”中的较矮者
想象你站在某个位置 i:
- 左边最高的柱子高度是
leftMax[i] - 右边最高的柱子高度是
rightMax[i]
那么这个位置上方能形成的“水位”就是:
👉 min(leftMax[i], rightMax[i])
而柱子本身占了 height[i] 的高度,所以实际能存的雨水为:
water[i] = Math.min(leftMax[i], rightMax[i]) - height[i]
💡 这就是经典的“木桶效应”:一只木桶能装多少水,取决于最短的那块木板。
✅ 解题三步走
第一步:从左到右,构建 left 数组
left[i] 表示从位置 0 到 i 的最大高度。
第二步:从右到左,构建 right 数组
right[i] 表示从位置 n-1 到 i 的最大高度。
第三步:遍历每个位置,累加雨水量
对每个 i,计算:
res += Math.min(left[i], right[i]) - height[i]
🧪 举个例子
以 height = [0,1,0,2,1,0,1,3,2,1,2,1] 为例:
left = [0,1,1,2,2,2,2,3,3,3,3,3]right = [3,3,3,3,3,3,3,3,2,2,2,1]
逐位计算后,总雨水量为 6。
💻 完整可运行代码(JavaScript)
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const n = height.length;
if (n === 0) return 0;
// Step 1: 构建 left 数组 —— 从左到右的最大高度
const left = new Array(n);
left[0] = height[0];
for (let i = 1; i < n; i++) {
left[i] = Math.max(height[i], left[i - 1]);
}
// Step 2: 构建 right 数组 —— 从右到左的最大高度
const right = new Array(n);
right[n - 1] = height[n - 1];
for (let i = n - 2; i >= 0; i--) {
right[i] = Math.max(height[i], right[i + 1]);
}
// Step 3: 计算每个位置的雨水量并累加
let res = 0;
for (let i = 0; i < n; i++) {
res += Math.min(left[i], right[i]) - height[i];
}
return res;
};
✅ 你可以直接复制这段代码到 LeetCode 提交,100% 通过!
⏱️ 复杂度分析
- 时间复杂度:O(n)
三次线性遍历,效率很高。 - 空间复杂度:O(n)
使用了两个辅助数组,但逻辑清晰、易于理解。
虽然存在 O(1) 空间的双指针优化解法,但对于掌握问题本质来说,这种“预处理+查表”的方式是最直观、最推荐的入门解法。
❓ 为什么这样是对的?
因为:
- 水不会从高处往低处“溢出”——它被左右两边的“墙”挡住;
- 实际水位由较矮的那一侧决定;
- 所以每个位置的理论最大水位 =
min(左侧最高, 右侧最高); - 减去柱子本身高度,剩下的就是“空出来的雨水空间”。
✅ 总结
解决“接雨水”问题的关键在于理解:
每个位置的积水 = 左右两侧最高墙的较小值 - 当前柱子高度
通过构建两个辅助数组,我们把复杂的动态判断转化为简单的查表操作,既高效又易懂。
掌握这个思路,不仅你能轻松拿下这道经典题,还能举一反三应对类似“区间最值”“边界约束”类问题!