42. 接雨水

这题是经典中的经典,核心就一句话:

某个位置能接多少水,取决于它左边最高柱子 和 右边最高柱子 中较矮的那个。

因为水会从低的那边流走。


比如:

复制代码
height = [4,2,0,3,2,5]

0 这个位置:

复制代码
4 2 0 3 2 5
    ^

它左边最高是 4

右边最高是 5

能装水高度:

复制代码
min(4,5)=4

当前位置高度是 0

所以这里能接:

复制代码
4 - 0 = 4

一、暴力思路(帮助理解)

对于每个位置:

  • 找左边最大值

  • 找右边最大值

  • 算当前能接多少水

公式:

复制代码
water[i] = min(leftMax,rightMax) - height[i]

但这样每个位置都遍历一次左右:

  • 时间复杂度 O(n²)

太慢。


二、前缀最大值优化(推荐先理解)

我们提前把:

  • 每个位置左边最高

  • 每个位置右边最高

都算出来。


1. leftMax数组

复制代码
leftMax[i] 表示:
i位置左边(包含自己)的最高柱子

例如:

复制代码
height = [4,2,0,3,2,5]

leftMax:
[4,4,4,4,4,5]

因为:

  • 到2位置时,左边最高还是4

  • 到3位置时,最高还是4

  • 到5位置时,变成5


2. rightMax数组

复制代码
rightMax[i]
表示i位置右边(包含自己)的最高柱子

结果:

复制代码
[5,5,5,5,5,5]

3. 计算每个位置接水量

公式:

复制代码
min(leftMax[i], rightMax[i]) - height[i]

三、完整代码(最容易理解)

java 复制代码
class Solution {
    public int trap(int[] height) {
        int n = height.length;

        if (n == 0) return 0;

        int[] leftMax = new int[n];
        int[] rightMax = new int[n];

        // 计算左边最大值
        leftMax[0] = height[0];
        for (int i = 1; i < n; i++) {
            leftMax[i] = Math.max(leftMax[i - 1], height[i]);
        }

        // 计算右边最大值
        rightMax[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            rightMax[i] = Math.max(rightMax[i + 1], height[i]);
        }

        // 计算雨水
        int ans = 0;

        for (int i = 0; i < n; i++) {
            ans += Math.min(leftMax[i], rightMax[i]) - height[i];
        }

        return ans;
    }
}

四、为什么公式一定成立?

因为:

当前位置最终水位:

复制代码
由左右两边更矮的墙决定

例如:

复制代码
5   2

虽然左边5很高,

但右边只有2,

水到了2就流走了。

所以水位只能是:

复制代码
min(5,2)=2

这是整题最核心的思想。


五、双指针(面试最优解)

空间还能优化。

因为我们其实不需要整个数组。

只需要:

  • 当前左边最大值

  • 当前右边最大值

即可。


核心思想(非常重要)

如果:

复制代码
leftMax < rightMax

那么:

当前位置能接多少水,

已经只由 leftMax 决定了。

因为右边一定至少有个更高的。

所以:

复制代码
water = leftMax - height[left]

然后移动左指针。

反之移动右指针。


六、双指针代码(重点)

复制代码
class Solution {
    public int trap(int[] height) {

        int left = 0;
        int right = height.length - 1;

        int leftMax = 0;
        int rightMax = 0;

        int ans = 0;

        while (left < right) {

            leftMax = Math.max(leftMax, height[left]);
            rightMax = Math.max(rightMax, height[right]);

            // 哪边小处理哪边
            if (leftMax < rightMax) {

                ans += leftMax - height[left];
                left++;

            } else {

                ans += rightMax - height[right];
                right--;
            }
        }

        return ans;
    }
}

七、为什么"双指针只看矮的一边"是对的?

这是这题最难理解的地方。

比如:

复制代码
leftMax = 3
rightMax = 5

当前位置左边水位:

最终一定不会超过3。

因为短板是左边。

右边已经够高了。

所以:

复制代码
当前位置接水量
= 3 - 当前高度

右边具体是不是5、8、100,

已经不重要。

所以可以直接结算左边。


八、这题本质是什么?

本质是:

找"当前位置左右两边的边界"

类似:

  • 木桶短板

  • 区间最小限制

  • 左右夹逼

这是很多困难题的核心思想。


这题建议你按这个顺序掌握:

  1. 暴力法(理解)

  2. 前后缀最大值(标准解)

  3. 双指针(真正掌握)

相关推荐
Controller-Inversion1 小时前
33. 搜索旋转排序数组
数据结构·算法·leetcode
陆水A1 小时前
运输时效预测模型:静态路由时效的计算与验证
大数据·人工智能·算法·spark·数据库开发·etl工程师
电科一班林耿超1 小时前
机器学习大师课 第 6 课:随机森林 —— 工业界最能打的 “万能算法“
算法·随机森林·机器学习
驼同学.1 小时前
【求职季】LeetCode Hot 100 渐进式扫盲手册(Python版)
python·算法·leetcode
宵时待雨2 小时前
优选算法专题6:模拟
数据结构·c++·算法·leetcode·职场和发展
Liangwei Lin2 小时前
LeetCode 35. 搜索插入位置
数据结构·算法·leetcode
数智工坊2 小时前
【经典RL算法】Q-Learning:强化学习的里程碑——从理论到收敛证明的完整解析
论文阅读·人工智能·深度学习·算法·transformer
叼烟扛炮2 小时前
C++ 知识点19 匿名对象
开发语言·c++·算法·匿名对象
叼烟扛炮2 小时前
C++ 知识点23 类模板
开发语言·c++·算法·类模版