在问 · 算法题解

407. 接雨水 II

困难栈与堆最小堆从边界向内扩展力扣原题 ↗

题目描述

输入一个 m x n 的非负整数矩阵 heightMap。每个格子是底面为 1 x 1 的立柱,数值为它的高度,整体构成一块起伏的地形。

下雨后,水会积在被更高地形围住的低洼处,而能从边界流走的水留不住。

返回整块地形最多能存住的雨水总体积。

示例 1
输入:heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出:4

解释:中间几处凹陷分别存住 1、2、1 个单位的水,共 4。

示例 2
输入:heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
输出:10

解释:外圈高 3;内圈 8 个高 2 的格子各存 1,中心高 1 的格子存 2,共 10。

数据范围

  • m == heightMap.length
  • n == heightMap[i].length
  • 1 <= m, n <= 200
  • 0 <= heightMap[i][j] <= 2 * 10^4

解题思路:最小堆从边界向内扩展

水能不能留住取决于“围墙”中最矮的那一块。先把最外一圈格子放进小根堆当围墙。每次弹出最矮的墙 h,检查它的未访问邻居:邻居比 h 低就能装 h − 高度 的水;然后邻居以 max(h, 自身高度) 的“有效高度”成为新围墙入堆。这样围墙从外向内收缩,每个格子只处理一次。

为什么正确

堆中每个格子的值 = 从外界到达它所需翻越的最低水位;弹出顺序按这个水位从低到高。

复杂度分析

时间 O(mn log(mn)),空间 O(mn)。

易错点

不能像一维那样只看行或列的左右最大值:水会从任意方向绕出去,必须用“最低围墙”从外向内推进;邻居入堆时要用 max(h, 高度),否则会低估水位。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def trapRainWater(self, heightMap: List[List[int]]) -> int:
        m, n = len(heightMap), len(heightMap[0])
        seen = [[False] * n for _ in range(m)]
        heap = []
        for i in range(m):
            for j in range(n):
                if i == 0 or j == 0 or i == m - 1 or j == n - 1:
                    heapq.heappush(heap, (heightMap[i][j], i, j))
                    seen[i][j] = True
        water = 0
        while heap:
            h, i, j = heapq.heappop(heap)
            for x, y in ((i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1)):
                if 0 <= x < m and 0 <= y < n and not seen[x][y]:
                    seen[x][y] = True
                    water += max(0, h - heightMap[x][y])
                    heapq.heappush(heap, (max(h, heightMap[x][y]), x, y))
        return water

载入编辑器,自己跑一遍 →

其他解法

最小堆每次从最低的边界向内扩展,每个格子只处理一次;广度优先搜索反复松弛水面高度,实现简单但同一格子可能入队多次。

解法 2:广度优先搜索迭代水位 时间 O(m²n²),空间 O(mn)

设 water[i][j] 为格子接水后的水面高度。边界格子的水面等于自身高度,其余格子先设为整个矩阵的最大高度。从边界格子出发做广度优先搜索:相邻格子的水面高于当前格子水面且高于它自身高度时,把它降到 max(当前水面, 它的高度),并重新入队。队列为空时水面稳定,答案是所有 water[i][j] - heightMap[i][j] 之和。

不需要堆,思路是不断让水从低处流走直到稳定。一个格子可能被多次更新,最坏复杂度高于堆方法,实际数据上通常也能通过。

from collections import deque


class Solution:
    def trapRainWater(self, heightMap):
        m, n = len(heightMap), len(heightMap[0])
        top = max(max(row) for row in heightMap)
        water = [[top] * n for _ in range(m)]
        q = deque()
        for i in range(m):
            for j in range(n):
                if i == 0 or j == 0 or i == m - 1 or j == n - 1:
                    water[i][j] = heightMap[i][j]
                    q.append((i, j))
        while q:
            i, j = q.popleft()
            for x, y in ((i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1)):
                if 0 <= x < m and 0 <= y < n:
                    if water[x][y] > water[i][j] and water[x][y] > heightMap[x][y]:
                        water[x][y] = max(water[i][j], heightMap[x][y])
                        q.append((x, y))
        return sum(water[i][j] - heightMap[i][j] for i in range(m) for j in range(n))

栈与堆 专题的其他题目

查看 栈与堆 全部题目 →