接雨水问题详解:用两个数组搞懂“短板决定容量”的核心思想

7 阅读3分钟

🌧️ 问题描述

给定一个非负整数数组 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] 表示从位置 0i 的最大高度。

第二步:从右到左,构建 right 数组

right[i] 表示从位置 n-1i 的最大高度。

第三步:遍历每个位置,累加雨水量

对每个 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(左侧最高, 右侧最高)
  • 减去柱子本身高度,剩下的就是“空出来的雨水空间”。

✅ 总结

解决“接雨水”问题的关键在于理解:

每个位置的积水 = 左右两侧最高墙的较小值 - 当前柱子高度

通过构建两个辅助数组,我们把复杂的动态判断转化为简单的查表操作,既高效又易懂。

掌握这个思路,不仅你能轻松拿下这道经典题,还能举一反三应对类似“区间最值”“边界约束”类问题!