Article / 文章

LeetCode第407题:接雨水 II

给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

难度:困难

题目链接:LeetCode第407题

题目描述

给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。

示例 1:

示例1

输入: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
输出: 4
解释: 下雨后,雨水将会被上图蓝色的方块中。总的接雨水量为4。

示例 2:

示例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.length
  • n == heightMap[i].length
  • 1 <= m, n <= 200
  • 0 <= heightMap[i][j] <= 2 * 104

解题思路

这道题目是经典题目“接雨水”的二维版本,核心思路是:

  1. 使用优先队列(最小堆)从外向内处理
  2. 维护一个“木桶”,其高度由最短的木板决定
  3. 从矩阵边界开始,按照高度从低到高的顺序处理每个位置

算法流程

  1. 初始化:

    • 创建优先队列,存储位置和高度
    • 将矩阵边界的所有位置加入队列
    • 使用visited数组标记已访问的位置
  2. BFS处理:

    • 每次从队列中取出高度最小的位置
    • 检查其四个相邻位置:
      • 如果相邻位置未访问:
        • 如果相邻位置高度小于当前高度,可以接水
        • 将相邻位置加入队列(使用max(当前高度, 相邻位置高度))
  3. 累计结果:

    • 对于每个可以接水的位置,计算可以接的水量
    • 水量 = 当前高度 - 位置原始高度

代码实现

C# 实现

public class Solution {
    public int TrapRainWater(int[][] heightMap) {
        if (heightMap == null || heightMap.Length <= 2 || heightMap[0].Length <= 2) {
            return 0;
        }
        
        int m = heightMap.Length;
        int n = heightMap[0].Length;
        bool[,] visited = new bool[m, n];
        
        // 使用优先队列(C#中使用SortedSet模拟)
        var pq = new SortedSet<(int height, int row, int col)>();
        
        // 添加边界
        for (int i = 0; i < m; i++) {
            pq.Add((heightMap[i][0], i, 0));
            pq.Add((heightMap[i][n-1], i, n-1));
            visited[i,0] = visited[i,n-1] = true;
        }
        for (int j = 1; j < n-1; j++) {
            pq.Add((heightMap[0][j], 0, j));
            pq.Add((heightMap[m-1][j], m-1, j));
            visited[0,j] = visited[m-1,j] = true;
        }
        
        int result = 0;
        int[] dx = {-1, 1, 0, 0};
        int[] dy = {0, 0, -1, 1};
        
        while (pq.Count > 0) {
            var curr = pq.Min;
            pq.Remove(curr);
            
            // 检查四个方向
            for (int k = 0; k < 4; k++) {
                int nx = curr.row + dx[k];
                int ny = curr.col + dy[k];
                
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx,ny]) {
                    visited[nx,ny] = true;
                    
                    // 如果新位置比当前高度低,可以接水
                    if (heightMap[nx][ny] < curr.height) {
                        result += curr.height - heightMap[nx][ny];
                        pq.Add((curr.height, nx, ny));
                    } else {
                        pq.Add((heightMap[nx][ny], nx, ny));
                    }
                }
            }
        }
        
        return result;
    }
}

Python 实现

class Solution:
    def trapRainWater(self, heightMap: List[List[int]]) -> int:
        if not heightMap or len(heightMap) <= 2 or len(heightMap[0]) <= 2:
            return 0
        
        m, n = len(heightMap), len(heightMap[0])
        visited = [[False] * n for _ in range(m)]
        
        # 使用优先队列
        pq = []
        
        # 添加边界
        for i in range(m):
            heapq.heappush(pq, (heightMap[i][0], i, 0))
            heapq.heappush(pq, (heightMap[i][n-1], i, n-1))
            visited[i][0] = visited[i][n-1] = True
        
        for j in range(1, n-1):
            heapq.heappush(pq, (heightMap[0][j], 0, j))
            heapq.heappush(pq, (heightMap[m-1][j], m-1, j))
            visited[0][j] = visited[m-1][j] = True
        
        result = 0
        directions = [(-1,0), (1,0), (0,-1), (0,1)]
        
        while pq:
            height, row, col = heapq.heappop(pq)
            
            # 检查四个方向
            for dx, dy in directions:
                nx, ny = row + dx, col + dy
                
                if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny]:
                    visited[nx][ny] = True
                    
                    # 如果新位置比当前高度低,可以接水
                    if heightMap[nx][ny] < height:
                        result += height - heightMap[nx][ny]
                        heapq.heappush(pq, (height, nx, ny))
                    else:
                        heapq.heappush(pq, (heightMap[nx][ny], nx, ny))
        
        return result

C++ 实现

class Solution {
public:
    int trapRainWater(vector<vector<int>>& heightMap) {
        if (heightMap.size() <= 2 || heightMap[0].size() <= 2) {
            return 0;
        }
        
        int m = heightMap.size();
        int n = heightMap[0].size();
        vector<vector<bool>> visited(m, vector<bool>(n, false));
        
        // 使用优先队列
        priority_queue<pair<int, pair<int, int>>, 
                      vector<pair<int, pair<int, int>>>,
                      greater<pair<int, pair<int, int>>>> pq;
        
        // 添加边界
        for (int i = 0; i < m; i++) {
            pq.push({heightMap[i][0], {i, 0}});
            pq.push({heightMap[i][n-1], {i, n-1}});
            visited[i][0] = visited[i][n-1] = true;
        }
        for (int j = 1; j < n-1; j++) {
            pq.push({heightMap[0][j], {0, j}});
            pq.push({heightMap[m-1][j], {m-1, j}});
            visited[0][j] = visited[m-1][j] = true;
        }
        
        int result = 0;
        vector<pair<int, int>> directions = {{-1,0}, {1,0}, {0,-1}, {0,1}};
        
        while (!pq.empty()) {
            auto curr = pq.top();
            pq.pop();
            int height = curr.first;
            int row = curr.second.first;
            int col = curr.second.second;
            
            // 检查四个方向
            for (const auto& dir : directions) {
                int nx = row + dir.first;
                int ny = col + dir.second;
                
                if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
                    visited[nx][ny] = true;
                    
                    // 如果新位置比当前高度低,可以接水
                    if (heightMap[nx][ny] < height) {
                        result += height - heightMap[nx][ny];
                        pq.push({height, {nx, ny}});
                    } else {
                        pq.push({heightMap[nx][ny], {nx, ny}});
                    }
                }
            }
        }
        
        return result;
    }
};

执行结果

C# 实现

  • 执行用时:124 ms
  • 内存消耗:44.2 MB

Python 实现

  • 执行用时:92 ms
  • 内存消耗:16.8 MB

C++ 实现

  • 执行用时:16 ms
  • 内存消耗:12.4 MB

性能对比

语言 执行用时 内存消耗 特点
C# 124 ms 44.2 MB 使用SortedSet模拟优先队列,性能较差
Python 92 ms 16.8 MB 使用heapq,代码简洁
C++ 16 ms 12.4 MB 原生优先队列,性能最优

代码亮点

  1. 🎯 使用优先队列实现从外向内的处理策略
  2. 💡 巧妙运用木桶原理解决二维接水问题
  3. 🔍 完善的边界条件处理
  4. 🎨 各语言实现都保持了良好的可读性

常见错误分析

  1. 🚫 忽略边界条件检查
  2. 🚫 优先队列使用最大堆而不是最小堆
  3. 🚫 访问标记处理不当
  4. 🚫 方向数组定义错误

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
优先队列BFS O(mnlog(m+n)) O(mn) 实现相对简单 空间消耗较大
动态规划 O(mn) O(mn) 理论性能好 不易实现,不直观

相关题目


📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第407题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!