题目描述
思路解析动画文字版
记住三句话:边界进小根堆、每次取最矮的边界往里看、邻居接水 = max(0, 边界高 减 自身高),然后以 max 抬高后入堆。下面逐帧套它。
接雨水 II:第一步:把整圈边界格都压进小根堆(蓝色=已入堆的边界)。这一圈就是初始的「围墙」,水要漏只能从它们里最矮的地方漏。这里边界全是 3,中间四格还空着(灰)。
接雨水 II:从堆里取出当前最矮的边界 (0,0)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (0,3)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (1,0)、高 3(琥珀)。看它还没访问的内邻居,水能不能从这里灌进去。
接雨水 II:往右看 (1,1)、高 1(紫),它比墙 3 矮,能接水 = 3 − 1 = 2(蓝水)。累计水量 = 2。再以有效高度 max(3, 1) = 3 把它压回堆,当新边界。
接雨水 II:从堆里取出当前最矮的边界 (1,3)、高 3(琥珀)。看它还没访问的内邻居,水能不能从这里灌进去。
接雨水 II:往左看 (1,2)、高 2(紫),它比墙 3 矮,能接水 = 3 − 2 = 1(蓝水)。累计水量 = 3。再以有效高度 max(3, 2) = 3 把它压回堆,当新边界。
接雨水 II:从堆里取出当前最矮的边界 (2,0)、高 3(琥珀)。看它还没访问的内邻居,水能不能从这里灌进去。
接雨水 II:往右看 (2,1)、高 2(紫),它比墙 3 矮,能接水 = 3 − 2 = 1(蓝水)。累计水量 = 4。再以有效高度 max(3, 2) = 3 把它压回堆,当新边界。
接雨水 II:从堆里取出当前最矮的边界 (2,3)、高 3(琥珀)。看它还没访问的内邻居,水能不能从这里灌进去。
接雨水 II:往左看 (2,2)、高 1(紫),它比墙 3 矮,能接水 = 3 − 1 = 2(蓝水)。累计水量 = 6。再以有效高度 max(3, 1) = 3 把它压回堆,当新边界。
接雨水 II:从堆里取出当前最矮的边界 (3,0)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (3,3)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (0,1)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (3,1)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (0,2)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (3,2)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (1,1)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (1,2)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (2,1)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:从堆里取出当前最矮的边界 (2,2)、高 3(琥珀)。它四周的格子要么是边界、要么早被访问过,没有新邻居可灌,直接丢弃,继续取下一个。
接雨水 II:堆空了,所有格子都被访问过(中间四个凹格都蓄上了蓝水)。把每格接到的水加起来:2 + 1 + 1 + 2 = 6。这就是整张图能接的雨水总量。
边界:行或列 ≤ 2 直接 0;中间凸起 0;四周高中间低才蓄水。
两点:四向漏水让边界变成一整圈,用最小堆维护;它本质是 Dijkstra 式的最小堆遍历。
参考代码
from typing import Listimport heapqclass Solution: def trapRainWater(self, heightMap: List[List[int]]) -> int: m, n = len(heightMap), len(heightMap[0]) if m < 3 or n < 3: return 0 seen = [[False] * n for _ in range(m)] heap = [] for r in range(m): for c in (0, n - 1): heapq.heappush(heap, (heightMap[r][c], r, c)); seen[r][c] = True for c in range(1, n - 1): for r in (0, m - 1): heapq.heappush(heap, (heightMap[r][c], r, c)); seen[r][c] = True ans = 0 while heap: h, r, c = heapq.heappop(heap) for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)): nr, nc = r + dr, c + dc if 0 <= nr < m and 0 <= nc < n and not seen[nr][nc]: seen[nr][nc] = True ans += max(0, h - heightMap[nr][nc]) heapq.heappush(heap, (max(h, heightMap[nr][nc]), nr, nc)) return ans复杂度
- 时间:O(mn·log(mn)),每个格子最多入堆、出堆一次,堆里最多 O(mn) 个元素,每次堆操作 O(log(mn)),总共 O(mn·log(mn))
- 空间:O(mn),seen 标记矩阵 O(mn),堆最坏也是 O(mn) 量级
易错点
面试追问把动画讲成自己的话
追问为什么二维接雨水不能像一维那样用双指针?
追问这个最小堆做法和最短路里的 Dijkstra 有什么关系?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
IPO
LeetCode 502 · 困难 · 沿着 堆套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题