一、题目回顾
题目编号:42题目名称:接雨水题目难度:困难输入示例:height = [0,1,0,2,1,0,1,3,2,1]输出示例:6
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
二、核心思想与思路
1. 核心思想
这道题的核心思路是:当前格子能接的水量 = min (左边最高柱子高度,右边最高柱子高度) - 当前柱子高度。只有当这个最小值大于当前柱子高度时,才能接到水。
2. 双指针优化思路
为了避免暴力法 O (n²) 的时间复杂度,我们使用双指针 + 维护左右最大高度的方式,将时间复杂度优化到 O (n),空间复杂度 O (1):
-
定义两个指针
left和right,分别从数组两端向中间移动 -
维护
left_max:left指针走过的所有柱子中的最大高度 -
维护
right_max:right指针走过的所有柱子中的最大高度 -
谁小处理谁:
- 如果
left_max < right_max:说明当前left位置的接水量由left_max决定,计算后left右移 - 如果
right_max <= left_max:说明当前right位置的接水量由right_max决定,计算后right左移
- 如果
3. 直观理解
可以想象成:
- 左右两边都有 “墙”(
left_max和right_max) - 哪边的墙更矮,水就会从哪边 “漏” 出去,所以当前位置的接水量由更矮的那面墙决定
- 每次移动指针时,都更新对应方向的最大高度
三、代码实现(Python)
from typing import List
class Solution:
def trap(self, height: List[int]) -> int:
if not height:
return 0
left = 0
right = len(height) - 1
left_max = 0 # 记录左边走过的最高柱子
right_max = 0 # 记录右边走过的最高柱子
ans = 0 # 总接水量
while left < right:
# 更新左右最大高度
left_max = max(left_max, height[left])
right_max = max(right_max, height[right])
if left_max < right_max:
# 左边最高 < 右边最高 → 当前left位置的接水量由left_max决定
ans += left_max - height[left]
left += 1
else:
# 右边最高 ≤ 左边最高 → 当前right位置的接水量由right_max决定
ans += right_max - height[right]
right -= 1
return ans
四、关键知识点讲解
1. 双指针技巧(Two Pointers)
适用场景:
- 数组 / 字符串需要从两端向中间遍历
- 需要在 O (n) 时间、O (1) 空间内解决问题
- 问题满足 “哪边小 / 大就移动哪边” 的决策逻辑
核心优势:
- 避免了暴力法的重复计算
- 不需要额外空间存储预处理的最大 / 最小值数组
- 逻辑清晰,代码简洁
与本题的结合:接雨水问题中,暴力法需要对每个位置分别找左、右最大值,时间 O (n²);而双指针通过一次遍历,同时维护左右最大值,将时间优化到 O (n),空间 O (1)。
2. 单调栈解法(拓展知识点)
除了双指针,这道题还有一种经典解法:单调栈,时间复杂度同样 O (n),空间 O (n)。
核心思想:
- 维护一个单调递减栈,栈中存储柱子的索引
- 当遇到一个比栈顶元素高的柱子时,说明形成了 “凹槽”,可以计算接水量
- 弹出栈顶作为 “凹槽底部”,新栈顶作为 “左边界”,当前柱子作为 “右边界”
适用场景:
- 寻找 “下一个更大 / 更小元素”
- 处理 “凹槽” 类问题(如接雨水、柱状图中最大矩形)
与双指针的对比:
| 解法 | 时间复杂度 | 空间复杂度 | 核心思想 |
|---|---|---|---|
| 双指针 | O(n) | O(1) | 两端向中间,谁小处理谁 |
| 单调栈 | O(n) | O(n) | 维护递减栈,遇高则计算凹槽 |
3. 动态规划预处理(基础思路)
这是理解双指针解法的前置基础,也是最容易想到的思路:
- 预处理左最大数组:
left_max[i]表示i位置左边(包括i)的最大高度 - 预处理右最大数组:
right_max[i]表示i位置右边(包括i)的最大高度 - 遍历每个位置,接水量 =
min(left_max[i], right_max[i]) - height[i],累加得到结果
代码示例:
def trap_dp(height: List[int]) -> int:
if not height:
return 0
n = len(height)
left_max = [0]*n
right_max = [0]*n
left_max[0] = height[0]
for i in range(1, n):
left_max[i] = max(left_max[i-1], height[i])
right_max[-1] = height[-1]
for i in range(n-2, -1, -1):
right_max[i] = max(right_max[i+1], height[i])
ans = 0
for i in range(n):
ans += min(left_max[i], right_max[i]) - height[i]
return ans
优缺点:
- ✅ 思路直观,容易理解
- ❌ 需要 O (n) 额外空间存储两个最大数组
- 双指针解法正是在此基础上,将空间优化到 O (1)
五、总结与思考
-
接雨水问题的本质:每个位置的接水量由左右两侧更矮的墙决定
-
解法演进:
- 暴力法:O (n²) → 超时
- 动态规划预处理:O (n) 时间,O (n) 空间 → 可接受
- 双指针:O (n) 时间,O (1) 空间 → 最优解
- 单调栈:O (n) 时间,O (n) 空间 → 另一种经典思路
-
双指针的通用性:这种 “两端向中间、根据条件移动指针” 的思路,在很多数组问题中都有应用,比如:
- 两数之和 II(输入有序数组)
- 盛最多水的容器
- 反转字符串
六、示例运行
以输入 [0,1,0,2,1,0,1,3,2,1] 为例:
- 初始化
left=0,right=9,left_max=0,right_max=0,ans=0 - 逐步移动指针,计算每个位置的接水量
- 最终累加得到
ans=6,与题目输出一致