【LeetCode 热题 100】接雨水


精选专栏链接 🔗


欢迎订阅,点赞+关注,每日精进1%,与百万开发者共攀技术珠峰

更多内容持续更新中~



【LeetCode 热题 100】接雨水

📝题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例1

bash 复制代码
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6

解释:上面是由数组 0,1,0,2,1,0,1,3,2,1,2,1 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。

示例 2:

bash 复制代码
输入:height = [4,2,0,3,2,5]
输出:9

💡提示信息

  • n == height.length;
  • 1 <= n <= 2 ∗ 10 4 2 * 10^4 2∗104;
  • 0 <= heighti <= 10 5 10^5 105;

什么是木桶效应?

"木桶效应"(Cask Effect),也被称为"短板效应"或"木桶定律",是一个经典的管理学和系统论概念。简单来说,它的核心含义是:一只木桶能盛多少水,并不取决于最长的那块木板,而是取决于最短的那块木板。

这个理论由美国管理学家劳伦斯·J·彼得(Laurence J. Peter)提出,用以说明一个系统或组织的整体效能,往往受制于其最薄弱的环节。


解题思路解析

这道题的核心在于:对于数组中的任意一个位置 i,它能接到的雨水高度是多少?

根据木桶效应,位置 i 上方的水位高度,取决于它左侧最大高度右侧最大高度 中的较小值

公式如下:

water i = max ⁡ ( 0 , min ⁡ ( left_max i , right_max i ) − height i ) \text{water}i = \max(0, \min(\text{left\_max}i, \text{right\_max}i) - \text{height}i) wateri=max(0,min(left_maxi,right_maxi)−heighti)

  • left_max[i]:位置 i 左边(包含 i)最高的柱子高度;
  • right_max[i]:位置 i 右边(包含 i)最高的柱子高度;
  • height[i]:当前位置柱子的高度;
  • 如果计算结果小于 0,说明当前柱子高度比水位还高,接不到水,记为 0;

基于这个核心思路,有两种主要的实现方式。


解法一:动态规划 (预处理数组)

这是最直观的解法。我们可以预先计算每个位置的 left_maxright_max 并存储在数组中,这样在遍历时就可以 O ( 1 ) O(1) O(1) 时间获取。

算法步骤:

  1. 创建两个数组 leftMaxrightMax
  2. 从左向右扫描,填充 leftMaxleftMax[i] = max(leftMax[i-1], height[i])
  3. 从右向左扫描,填充 rightMaxrightMax[i] = max(rightMax[i+1], height[i])
  4. 最后遍历一次数组,累加每个位置的雨水量;

Java 代码实现

java 复制代码
class Solution {
    public int trap(int[] height) {
        if (height == null || height.length == 0) {
            return 0;
        }

        int n = height.length;
        int[] leftMax = new int[n];
        int[] rightMax = new int[n];

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

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

        // 3. 计算总雨水量
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int waterLevel = Math.min(leftMax[i], rightMax[i]) - height[i];
            ans += waterLevel ;
        }

        return ans;
    }
}

提交代码,运行结果如下

复杂度分析

时间复杂度: O ( N ) O(N) O(N),我们需要遍历三次数组,每次都是 O ( N ) O(N) O(N)的时间复杂度;

空间复杂度: O ( N ) O(N) O(N),我们需要两个额外的数组来存储最大高度。


解法二:双指针 (最优解)

方法一虽然快,但占用 O ( N ) O(N) O(N) 的额外空间。我们可以通过双指针 技巧将空间复杂度优化到 O ( 1 ) O(1) O(1)。

算法思路:

我们维护两个指针 leftright,分别从数组的两端向中间移动。同时维护两个变量 left_maxright_max,分别记录 left 指针左边扫过的最大值和 right 指针右边扫过的最大值。

关键逻辑:

依然根据公式:

water i = max ⁡ ( 0 , min ⁡ ( left_max i , right_max i ) − height i ) \text{water}i = \max(0, \min(\text{left\_max}i, \text{right\_max}i) - \text{height}i) wateri=max(0,min(left_maxi,right_maxi)−heighti)

在移动过程中,我们总是处理较矮那一侧的指针。

  • 如果 height[left] < height[right]
    • 由于算法每次都只移动较矮的一侧,能走到现在这个 left,说明左边从来没有被右边更高的柱子挡住而停滞不前;
    • 这意味着左边目前的最大值 left_max 一定小于右边的某个值(这个值可能是当前的 height[right] 或者 right_max);
    • 根据木桶效应,当前位置 left 的水位高度完全由 left_max 决定(因为短板在左边);
    • 我们可以直接计算 left 位置的雨水,然后将 left 右移;
  • 反之,如果 height[left] >= height[right]
    • 同理,右边目前的最大值 right_max 是短板。
    • 我们可以直接计算 right 位置的雨水,然后将 right 左移。

关健难点理解: 为什么 height[left] < height[right]时,左边目前的最大值 left_max 一定小于右边的某个值?

双指针每次只移动较矮的一侧 。假设我们成功地将左指针从起始位置 0 一路移动到了当前位置 left,这意味着在之前的每一步中,每当左指针指向某个位置时,它都满足"比当前右指针矮"的条件,从而被允许向右移动。如果曾经出现过"左指针指向的柱子比右指针高"的情况,那么算法就会去移动右指针,而左指针就会停在原地等待。

所以,左指针从未因为自己比右边高而被卡住;它每次都是因为自己比右边矮才得以向前移动。换句话说,右边始终有一个不低于左指针当前高度的墙(右指针位置)存在,才使得左指针能够不断前进。

height[left] < height[right] 成立时,左指针能走到当前位置,意味着在之前的每一步中,左指针指向的柱子始终比当时的右指针矮

Java 代码实习如下:

java 复制代码
class Solution {
    public int trap(int[] height) {
    	// 校验
        if (height == null || height.length == 0) {
            return 0;
        }

        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 (height[left] < height[right]) {
                // 左边矮,处理左边
                // 此时 leftMax 一定小于 rightMax,所以水位由 leftMax 决定
                ans += leftMax - height[left];
                left++;
            } else {
                // 右边矮(或相等),处理右边
                // 此时 rightMax 一定小于等于 leftMax,水位由 rightMax 决定
                ans += rightMax - height[right];
                right--;
            }
        }

        return ans;
    }
}

提交代码,运行结果如下:

复杂度分析

时间复杂度: O ( N ) O(N) O(N),双指针遍历一次数组;

空间复杂度: O ( 1 ) O(1) O(1),只需要常数级别的额外空间;


总结

方法 时间复杂度 空间复杂度 评价
动态规划 O ( N ) O(N) O(N) O ( N ) O(N) O(N) 逻辑简单,容易想到,适合面试时作为第一解法。
双指针 O ( N ) O(N) O(N) O ( 1 ) O(1) O(1) 最优解,对思维要求较高,体现了对"木桶效应"的深刻理解。

希望这篇解析能帮你彻底搞懂"接雨水"问题!

相关推荐
行者全栈架构师4 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_04 小时前
mac(m5)平台编译openjdk
java
JieE21213 小时前
LeetCode 56. 合并区间|超清晰 JS 图解思路,面试高频区间题
javascript·算法·面试
Jack2021 小时前
HarmonyOS开发中错误处理策略:网络异常统一处理
算法
小小杨树1 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
唐青枫1 天前
Java JDBC 实战指南:从 Connection 到事务和连接池
java
一个做软件开发的牛马1 天前
MyBatis-Plus 从零实战:完整搭建可运行 Demo,BaseMapper 零 SQL、Wrapper 条件构造、分页插件与代码生成器详解
java·后端
用户3721574261351 天前
Java 处理 PDF 图片:提取 PDF 中的图片,并压缩 PDF 图片体积
java
用户3721574261351 天前
Java 打印 Word 文档:从基础打印到高级设置
java
JieE2122 天前
LeetCode 226. 翻转二叉树|JS 递归超详细拆解,二叉树入门经典题
javascript·算法