Hard
题目描述
给你一个 m x n 的整数矩阵 heightMap ,它表示一个二维高程图中每个单元的高度,请计算下雨后能够接多少雨水。
示例 1:
输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,水被接在两个小池塘中,分别接了 1 和 3 单位的雨水。
总的接雨水量为 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
提示:
m == heightMap.lengthn == heightMap[i].length1 <= m, n <= 2000 <= heightMap[i][j] <= 2 * 10^4
解题思路
这道题是经典的"接雨水"问题的二维版本。关键思路是从边界开始,逐步向内部扩展,利用优先队列(最小堆)来确保每次处理的都是当前可到达的最低高度。
核心思想:
- 水能否被接住取决于四周的最低"挡板"高度
- 边界上的格子无法接水,作为起始点
- 使用优先队列维护当前可处理的格子,按高度从小到大排序
- 对于每个从队列中取出的格子,检查其四个相邻格子
- 如果相邻格子的高度小于当前水位,说明可以接水
算法步骤:
- 将所有边界格子加入优先队列,并标记为已访问
- 从队列中取出高度最小的格子作为当前处理点
- 遍历其四个方向的相邻格子
- 如果相邻格子未访问过,计算该位置能接的水量
- 将相邻格子加入队列,水位高度为
max(当前高度, 相邻格子原始高度) - 重复直到队列为空
这种方法确保了水总是从最低的出口开始"倾倒",符合物理直觉。
代码实现
class Solution {
public:
int trapRainWater(vector<vector<int>>& heightMap) {
int m = heightMap.size(), n = heightMap[0].size();
if (m <= 2 || n <= 2) return 0;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
vector<vector<bool>> visited(m, vector<bool>(n, false));
// 将边界加入优先队列
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 || i == m-1 || j == 0 || j == n-1) {
pq.push({heightMap[i][j], i * n + j});
visited[i][j] = true;
}
}
}
int directions[4][2] = {{0,1}, {1,0}, {0,-1}, {-1,0}};
int result = 0;
while (!pq.empty()) {
auto [height, pos] = pq.top();
pq.pop();
int x = pos / n, y = pos % n;
for (auto& dir : directions) {
int nx = x + dir[0], ny = y + dir[1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
result += max(0, height - heightMap[nx][ny]);
pq.push({max(height, heightMap[nx][ny]), nx * n + ny});
visited[nx][ny] = true;
}
}
}
return result;
}
};
class Solution:
def trapRainWater(self, heightMap: List[List[int]]) -> int:
import heapq
m, n = len(heightMap), len(heightMap[0])
if m <= 2 or n <= 2:
return 0
heap = []
visited = [[False] * n for _ in range(m)]
# 将边界加入堆
for i in range(m):
for j in range(n):
if i == 0 or i == m-1 or j == 0 or j == n-1:
heapq.heappush(heap, (heightMap[i][j], i, j))
visited[i][j] = True
directions = [(0,1), (1,0), (0,-1), (-1,0)]
result = 0
while heap:
height, x, y = heapq.heappop(heap)
for dx, dy in directions:
nx, ny = x + dx, y + dy
if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny]:
result += max(0, height - heightMap[nx][ny])
heapq.heappush(heap, (max(height, heightMap[nx][ny]), nx, ny))
visited[nx][ny] = True
return result
public class Solution {
public int TrapRainWater(int[][] heightMap) {
int m = heightMap.Length, n = heightMap[0].Length;
if (m <= 2 || n <= 2) return 0;
var pq = new PriorityQueue<(int height, int x, int y), int>();
var visited = new bool[m, n];
// 将边界加入优先队列
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 || i == m-1 || j == 0 || j == n-1) {
pq.Enqueue((heightMap[i][j], i, j), heightMap[i][j]);
visited[i, j] = true;
}
}
}
int[,] directions = {{0,1}, {1,0}, {0,-1}, {-1,0}};
int result = 0;
while (pq.Count > 0) {
var (height, x, y) = pq.Dequeue();
for (int d = 0; d < 4; d++) {
int nx = x + directions[d,0], ny = y + directions[d,1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx, ny]) {
result += Math.Max(0, height - heightMap[nx][ny]);
pq.Enqueue((Math.Max(height, heightMap[nx][ny]), nx, ny), Math.Max(height, heightMap[nx][ny]));
visited[nx, ny] = true;
}
}
}
return result;
}
}
var trapRainWater = function(heightMap) {
if (!heightMap || heightMap.length === 0 || heightMap[0].length === 0) return 0;
const m = heightMap.length;
const n = heightMap[0].length;
const visited = Array(m).fill().map(() => Array(n).fill(false));
const pq = new MinPriorityQueue({ priority: x => x.height });
// Add all boundary cells to priority queue
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (i === 0 || i === m - 1 || j === 0 || j === n - 1) {
pq.enqueue({ row: i, col: j, height: heightMap[i][j] });
visited[i][j] = true;
}
}
}
const directions = [[0, 1], [0, -1], [1, 0], [-1, 0]];
let water = 0;
while (!pq.isEmpty()) {
const { row, col, height } = pq.dequeue().element;
for (const [dr, dc] of directions) {
const newRow = row + dr;
const newCol = col + dc;
if (newRow >= 0 && newRow < m && newCol >= 0 && newCol < n && !visited[newRow][newCol]) {
visited[newRow][newCol] = true;
const waterLevel = Math.max(height, heightMap[newRow][newCol]);
water += waterLevel - heightMap[newRow][newCol];
pq.enqueue({ row: newRow, col: newCol, height: waterLevel });
}
}
}
return water;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(mn log(mn)) | 每个格子最多入队一次,堆操作时间复杂度为 O(log(mn)) |
| 空间复杂度 | O(mn) | 需要 visited 数组和优先队列存储格子信息 |