(LeetCode-Hot100)42. 接雨水

接雨水(LeetCode 42)

问题简介

🔗 LeetCode 42. 接雨水

题目描述

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


示例说明

示例 1:

复制代码
输入: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:

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

解题思路

💡 思路一:暴力法(不推荐,仅用于理解)

对每个柱子,找出其左边最大高度和右边最大高度,当前柱子能接的雨水为:

复制代码
water[i] = max(0, min(leftMax, rightMax) - height[i])
  • 时间复杂度高,O(n²),空间 O(1)

✅ 思路二:动态规划(预处理左右最大值)

  1. 创建两个数组 leftMaxrightMax
    • leftMax[i] 表示从 0i 的最大高度
    • rightMax[i] 表示从 in-1 的最大高度
  2. 再遍历一次,累加每个位置的雨水量

优点 :清晰易懂
缺点:需要额外 O(n) 空间


✅ 思路三:双指针(最优解)

利用双指针从两端向中间移动:

  • 维护 leftMaxrightMax
  • leftMax < rightMax,则左侧柱子的雨水量由 leftMax 决定(因为右侧有更高的墙)
  • 反之亦然

优点:时间 O(n),空间 O(1),效率最高


✅ 思路四:单调栈

使用单调递减栈存储索引:

  • 当遇到比栈顶高的柱子时,说明可以形成凹槽
  • 弹出栈顶,计算以该位置为底的雨水面积

适用于"找最近更大元素"类问题,但本题中不如双指针简洁


代码实现

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];
        
        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 water = 0;
        for (int i = 0; i < n; i++) {
            water += Math.min(leftMax[i], rightMax[i]) - height[i];
        }
        return water;
    }
}

// 方法二:双指针(推荐)
class Solution {
    public int trap(int[] height) {
        if (height == null || height.length == 0) return 0;
        int left = 0, right = height.length - 1;
        int leftMax = 0, rightMax = 0;
        int water = 0;
        
        while (left < right) {
            if (height[left] < height[right]) {
                if (height[left] >= leftMax) {
                    leftMax = height[left];
                } else {
                    water += leftMax - height[left];
                }
                left++;
            } else {
                if (height[right] >= rightMax) {
                    rightMax = height[right];
                } else {
                    water += rightMax - height[right];
                }
                right--;
            }
        }
        return water;
    }
}
go:Go 复制代码
// 方法一:动态规划
func trap(height []int) int {
    n := len(height)
    if n == 0 {
        return 0
    }
    
    leftMax := make([]int, n)
    rightMax := make([]int, n)
    
    leftMax[0] = height[0]
    for i := 1; i < n; i++ {
        leftMax[i] = max(leftMax[i-1], height[i])
    }
    
    rightMax[n-1] = height[n-1]
    for i := n - 2; i >= 0; i-- {
        rightMax[i] = max(rightMax[i+1], height[i])
    }
    
    water := 0
    for i := 0; i < n; i++ {
        water += min(leftMax[i], rightMax[i]) - height[i]
    }
    return water
}

// 方法二:双指针(推荐)
func trap(height []int) int {
    if len(height) == 0 {
        return 0
    }
    
    left, right := 0, len(height)-1
    leftMax, rightMax := 0, 0
    water := 0
    
    for left < right {
        if height[left] < height[right] {
            if height[left] >= leftMax {
                leftMax = height[left]
            } else {
                water += leftMax - height[left]
            }
            left++
        } else {
            if height[right] >= rightMax {
                rightMax = height[right]
            } else {
                water += rightMax - height[right]
            }
            right--
        }
    }
    return water
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

示例演示

height = [0,1,0,2,1,0,1,3,2,1,2,1] 为例:

i height[i] leftMax rightMax min(L,R) water[i]
0 0 0 3 0 0
1 1 1 3 1 0
2 0 1 3 1 1
3 2 2 3 2 0
4 1 2 3 2 1
5 0 2 3 2 2
6 1 2 3 2 1
7 3 3 3 3 0
8 2 3 2 2 0
9 1 3 2 2 1
10 2 3 2 2 0
11 1 3 1 1 0

✅ 总雨水 = 1+1+2+1+1 = 6


答案有效性证明

关键观察

一个位置 i 能接的雨水高度,取决于其左侧最高墙右侧最高墙中的较小者。

引理 :若 leftMax[i] ≤ rightMax[i],则 i 处的积水高度为 leftMax[i] - height[i](前提是 leftMax[i] > height[i]

双指针正确性

  • height[left] < height[right] 时,leftMax ≤ rightMax 必然成立(因为 rightMax 至少为 height[right]
  • 因此 left 位置的积水由 leftMax 决定,无需知道全局 rightMax
  • 同理适用于右侧

通过数学归纳法可严格证明算法正确性。


复杂度分析

方法 时间复杂度 空间复杂度 说明
暴力法 O(n²) O(1) 每个位置遍历左右
动态规划 O(n) O(n) 两次预处理 + 一次计算
双指针 O(n) O(1) 最优解,推荐使用
单调栈 O(n) O(n) 每个元素入栈出栈一次

问题总结

📌 核心思想:每个位置的积水由"短板效应"决定------即左右两侧最高墙的较小值。

最佳实践:双指针法在保证线性时间的同时,将空间压缩到常数级别,是面试和竞赛中的首选解法。

💡 延伸思考

  • 二维接雨水(LeetCode 407)可用优先队列(最小堆)解决
  • 若允许修改原数组,可进一步优化空间(但通常不建议)

掌握本题的多种解法,有助于深入理解前缀/后缀最值双指针技巧单调栈的应用场景。

github地址: https://github.com/swf2020/LeetCode-Hot100-Solutions

相关推荐
lifallen1 小时前
点分治 (Centroid Decomposition)
java·数据结构·算法
jimy12 小时前
小腿三头肌--人体第二心脏;踝关节屈伸环绕运动让小腿三头肌收缩,促进血液回流心脏,降低饭后血糖,减少饭困
职场和发展·程序员创富
Hx_Ma163 小时前
测试题(三)
java·开发语言·后端
田里的水稻3 小时前
FA_规划和控制(PC)-瑞德斯.谢普路径规划(RSPP))
人工智能·算法·数学建模·机器人·自动驾驶
罗湖老棍子3 小时前
【例 1】二叉苹果树(信息学奥赛一本通- P1575)
算法·树上背包·树型动态规划
元亓亓亓4 小时前
LeetCode热题100--76. 最小覆盖子串--困难
算法·leetcode·职场和发展
CHANG_THE_WORLD4 小时前
C++数组地址传递与数据影响:深入理解指针与内存
算法
json{shen:"jing"}4 小时前
力扣-单词拆分
数据结构·算法
星火开发设计4 小时前
序列式容器:deque 双端队列的适用场景
java·开发语言·jvm·c++·知识