Leetcode42:接雨水

0 阅读5分钟

前言
在前端算法面试中,LeetCode 42 题《接雨水》绝对是门神级别的存在。
标记为 Hard,实际上考的是你对空间复杂度的极致追求。很多同学虽然能用暴力法或动态规划做出来,但在面试官追问“能不能把空间优化到 O(1)?”时,往往会卡壳。

今天,我们就用最通俗的大白话,拆解这道题的双指针最优解。看完这篇,保证你下次面试时能自信地在白板上默写出来!


一、 核心思维:木桶效应

做这道题,首先要抛开复杂的算法名词,回归物理常识。

想象一下,每一个位置 i 能装多少水,取决于什么?
它取决于它左边最高的墙 (leftMax)  和 它右边最高的墙 (rightMax)

这就好比“木桶效应”:盛水的多少,取决于最短的那块板。

对于位置 i,它能接到的雨水公式是:

Water[i]=min⁡(LeftMax,RightMax)−height[i]

(当然,如果计算结果小于 0,说明这个位置本身就是最高的,存不住水,取 0 即可)


二、 解法演进:从 O(N) 到 O(1)

1. 也是好解法:动态规划 (空间 O(N))

最直观的思路是:既然需要知道每个位置左边和右边的最大高度,那我开两个数组 leftMaxArr 和 rightMaxArr,先把所有位置的左右最高墙存下来,最后遍历计算。

  • 优点:逻辑简单,直觉符合。
  • 缺点:需要额外的数组空间,空间复杂度 O(N)

面试官的追问

“你的思路很清晰。但是,如果内存非常有限,不允许开辟新数组,你能用 O(1) 的空间复杂度解决吗?

2. 最优解法:双指针 (空间 O(1))

这时候,双指针闪亮登场。
我们真的需要把所有位置的 Max 都存下来吗?其实不需要。我们只需要维护两个指针 left 和 right,以及两个变量 leftMax 和 rightMax,一边遍历一边计算。


三、 代码深度解析(JS 版)

先上代码,这是标准的教科书级写法,建议背诵全文(误):

JavaScript

var trap = function(height) {
    let ans = 0;
    let left = 0, right = height.length - 1;
    let leftMax = 0, rightMax = 0;

    while (left < right) {
        // 1. 更新左右两侧遇到的最大高度
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);

        // 2. 核心判断:哪边低,就处理哪边
        if (height[left] < height[right]) {
            // 左边是短板,由 leftMax 决定水位
            ans += leftMax - height[left];
            ++left;
        } else {
            // 右边是短板,由 rightMax 决定水位
            ans += rightMax - height[right];
            --right;
        }
    }
    return ans;
};

难点攻克:为什么 if 这样写就能算对?

很多同学看代码时,最大的困惑在这里:

JavaScript

if (height[left] < height[right]) {
    ans += leftMax - height[left];
}

疑问:计算 left 位置的水量,公式不是 min(leftMax, rightMax) - height[left] 吗?这里我们只知道 leftMax,怎么确定 rightMax 一定比 leftMax 大呢?万一中间有个很矮的坑呢?

深度解析
这正是双指针的精妙之处!请跟紧我的思路:

  1. 前提:rightMax 是随着 right 指针从右向左移动更新的,它代表了 right 右侧(包含自身)的最高值。

  2. 推导

    • 如果我们进入了 if (height[left] < height[right]) 这个分支。
    • 这意味着 height[left] 小于 height[right]。
    • 而 rightMax 必然是大等于 height[right] 的。
    • 结论:leftMax (当前算出来的左边最大) 肯定小于 rightMax (右边目前已知的最大)。

通俗比喻
我们要填满一个坑,左边的墙高 5 米,右边的墙高 10 米。
虽然我们不知道右边 10 米墙的后面是不是还有更高的山,或者更深的坑,但这都不重要
因为短板在左边(5米)
只要右边有一堵墙比左边高(哪怕只高一点点),当前这个位置的水位就被左边的墙锁死在 5 米了。

所以,我们只需要放心地用 leftMax - height[left] 计算即可,右边不用管!


四、 画面模拟

假设数组是 [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]。

  1. 初始:left 指向 0,right 指向 1。

  2. 第一轮:height[left] (0) < height[right] (1)。

    • 短板在左。
    • leftMax 更新为 0。
    • 装水 0 - 0 = 0。
    • left 右移。

屏幕截图 2026-01-21 132854.png

  1. ...中间过程...

  2. 关键时刻:假设 left 走到了下标 2(高度0),此时 leftMax 是 1(下标1的高度)。right还在很远的高处(比如高度3)。

    • 因为 leftMax (1) < rightMax (3)。
    • 我们确定下标 2 能装水:1 - 0 = 1。
    • 完全不需要知道 right 左边具体是啥,只要知道右边有个比我高的爹就行。

五、 面试“显眼包”总结

如果面试官让你总结这道题,你可以甩出这段满分话术

“这道题的核心在于短板效应

也就是位置 i 的积水量由左右两边较矮的那个最高值决定。

相比于动态规划需要维护两个数组的 O(N)  空间,我选择使用双指针

我们从两头向中间靠拢,哪边低,哪边就是瓶颈。如果左边低,那左边的 leftMax 就是实际水位上限,我们算出左边的积水并向右移动;反之亦然。

这样我们可以做到 时间复杂度 O(N)  且 空间复杂度 O(1)  的最优解。”


结语

接雨水看似困难,其实捅破了窗户纸,就是两个指针在比谁的墙更矮而已。

希望这篇解析能让你对双指针有更深的理解!如果你觉得有帮助,欢迎点赞 + 收藏,这对我非常重要!我们下个算法题见!

(P.S. 别忘了亲自去 LeetCode 手敲一遍哦!)