当前位置: 首页 > news >正文

LeetCode 407:接雨水 II

LeetCode 407:接雨水 II

在这里插入图片描述

问题本质:二维空间的边界约束

与一维接雨水(仅受左右边界限制)不同,二维接雨水的每个位置受四周最低边界的约束。例如,一个单元格能存多少水,取决于周围“最低的墙”——只有当周围存在更低的边界时,水才会被限制在该高度以下。

核心思路:优先队列(最小堆)驱动的边界扩展

关键观察

水的高度由 当前最低的边界 决定。例如,若当前边界的最小高度是 h,则内部所有比 h 低的单元格最多能存 h - height 的水(height 是单元格自身高度)。

算法逻辑
  1. 初始化边界:矩阵的外围单元格无法存水(水会流走),因此作为初始边界。
  2. 维护最小堆:将边界单元格按高度排序,每次取出 高度最小的边界(因为它是当前最严格的约束)。
  3. 扩展边界:遍历当前边界的四个邻居,根据邻居高度与当前边界高度的关系:
    • 若邻居更低:计算存水量(当前边界高度 - 邻居高度),并将邻居以 当前边界高度 加入堆(存水后,该邻居的上表面成为新边界)。
    • 若邻居更高:邻居自身成为新边界,以 自身高度 加入堆。

算法步骤详解

1. 数据结构与初始化
  • 优先队列(最小堆):存储 [高度, 行, 列],按高度升序排列(确保每次处理最低边界)。
  • 访问标记visited 数组标记单元格是否已处理,避免重复计算。
  • 边界初始化:将矩阵的第一行、最后一行、第一列、最后一列加入堆,并标记为已访问。
int m = heightMap.length;
int n = heightMap[0].length;
boolean[][] visited = new boolean[m][n];
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);// 初始化边界(外围单元格)
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.offer(new int[]{heightMap[i][j], i, j});visited[i][j] = true;}}
}
2. 方向数组与核心循环

定义四个方向(上、下、左、右),遍历当前边界的邻居:

int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; // 四个方向
int res = 0;while (!pq.isEmpty()) {int[] curr = pq.poll(); // 取出当前最低边界int currHeight = curr[0], x = curr[1], y = curr[2];for (int[] dir : dirs) {int nx = x + dir[0], ny = y + dir[1]; // 邻居坐标// 检查邻居是否在矩阵内且未被访问if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {visited[nx][ny] = true; // 标记为已访问if (heightMap[nx][ny] < currHeight) {// 邻居更低,计算存水量res += currHeight - heightMap[nx][ny];// 存水后,邻居的上表面高度为 currHeight(作为新边界)pq.offer(new int[]{currHeight, nx, ny});} else {// 邻居更高,自身成为新边界pq.offer(new int[]{heightMap[nx][ny], nx, ny});}}}
}

关键逻辑解析

  1. 为什么用最小堆?
    最小堆确保每次处理当前最低的边界。因为低边界是存水的“瓶颈”——只有突破低边界,存水高度才会被更高的边界限制。先处理低边界,能保证存水计算的准确性。

  2. 存水的计算条件
    当邻居高度 < 当前边界高度 时,水会被当前边界“拦住”,存水量为 currHeight - heightMap[nx][ny]。此时,邻居的上表面高度等于当前边界高度(水的高度),因此将其以 currHeight 加入堆,作为新边界。

  3. 边界的动态扩展
    若邻居更高,则它自身成为新的边界(水无法超过它),因此以自身高度加入堆;若邻居更低,则存水后以当前边界高度作为新边界(水的高度由当前边界决定)。

复杂度分析

  • 时间复杂度O(mn log(mn))

    • 每个单元格最多入堆、出堆各一次,堆操作的时间复杂度为 O(log(mn))(堆的最大规模为 mn)。
    • 遍历所有单元格和方向的时间为 O(mn)
  • 空间复杂度O(mn)

    • visited 数组占用 O(mn) 空间。
    • 优先队列最多存储 mn 个单元格,空间复杂度为 O(mn)

示例模拟(以题目示例1为例)

输入

heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]
]
初始化边界(外围单元格)

将第一行、第三行(索引0和2)、第一列、第六列(索引0和5)的单元格加入堆。初始堆中的元素按高度排序,例如 (1,0,0)(1,0,3)(2,0,5) 等。

第一次处理堆顶(最小高度,如 (1,0,0)
  • 邻居为 (0,1)(高度4)和 (1,0)(高度3)。
  • (0,1) 高度4 > 1,加入堆 [4,0,1]
  • (1,0) 高度3 > 1,加入堆 [3,1,0]
  • 无存水(邻居更高)。
处理下一个堆顶(如 (1,0,3)
  • 邻居为 (0,2)(高度3)、(0,4)(高度3)、(1,3)(高度3)。
  • 所有邻居高度 > 1,加入堆,无存水。
处理堆顶 (2,0,5)(高度2)
  • 邻居为 (0,4)(高度3,已访问)、(1,5)(高度4)。
  • (1,5) 高度4 > 2,加入堆。
后续处理中,当遇到低边界时计算存水

例如,假设堆顶为 (2,1,1)(高度2,来自中间区域),其邻居 (1,2) 高度1 < 2:

  • 存水量:2 - 1 = 1,累加到 res
  • (2,1,2) 加入堆(存水后高度为2)。

通过不断扩展边界并计算存水,最终累加得到结果 4(与题目示例一致)。

总结:二维接雨水的本质是“边界约束”

该算法通过 最小堆动态维护最低边界,模拟了水在二维空间中“从最低处开始积累”的过程。核心思想是:水的高度由当前最严格的(最低)边界决定,通过优先处理低边界,确保存水计算的高效与准确。

这种方法不仅解决了二维接雨水问题,还体现了“贪心 + 优先队列”在处理动态边界约束问题中的经典应用,可推广到类似的空间约束问题(如洪水填充、区域扩展等)。

完整代码

import java.util.PriorityQueue;
import java.util.Arrays;class Solution {public int trapRainWater(int[][] heightMap) {if (heightMap == null || heightMap.length == 0 || heightMap[0].length == 0) {return 0;}int m = heightMap.length;int n = heightMap[0].length;boolean[][] visited = new boolean[m][n];// 最小堆:存储 [高度, 行, 列],按高度升序排列PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);// 初始化边界(第一行、最后一行、第一列、最后一列)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.offer(new int[]{heightMap[i][j], i, j});visited[i][j] = true;}}}// 四个方向:上、下、左、右int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};int res = 0;while (!pq.isEmpty()) {int[] curr = pq.poll();int currHeight = curr[0], x = curr[1], y = curr[2];for (int[] dir : dirs) {int nx = x + dir[0];int ny = y + dir[1];// 检查邻居是否在矩阵内且未被访问if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {visited[nx][ny] = true;if (heightMap[nx][ny] < currHeight) {// 邻居高度低于当前边界,可接水:边界高度 - 邻居高度res += currHeight - heightMap[nx][ny];// 新边界高度仍为 currHeight(水的高度由当前边界决定)pq.offer(new int[]{currHeight, nx, ny});} else {// 邻居高度高于当前边界,自身成为新边界pq.offer(new int[]{heightMap[nx][ny], nx, ny});}}}}return res;}
}
http://www.dtcms.com/a/294614.html

相关文章:

  • 网络编程初识(详细易懂)
  • [强网杯 2019]高明的黑客
  • iOS加固工具有哪些?企业级团队协作视角的实战分析
  • 【浏览器插件冲突】Edge浏览器加载不出来CSDN创作者中心
  • SpringBoot07-数据层的解决方案:SQL
  • 【科研绘图系列】R语言绘制黑白填充等显著性标记条形图
  • 华为仓颉编程语言实践体验
  • 基于Springboot的中药商城管理系统/基于javaweb的中药材销售系统
  • 海外短剧系统开发:技术架构与市场机遇深度解析
  • 华为7月23日机考真题
  • 华为高频算法题:最长连续递增子序列(Longest Continuous Increasing Subsequence)
  • 【JVM】从 JVM 整体说明 JVM 运行的完整流程
  • Redis MCP 安装与配置完整指南
  • 83、设置有人DTU设备USR-M100采集传感器数据,然后上传阿里云服务
  • 卷积神经网络:模型评估标准
  • Qt容器类:QList、QMap等的高效使用
  • 2025年7月份实时最新获取地图边界数据方法,省市区县街道多级联动【文末附实时geoJson数据下载】
  • 闲庭信步使用图像验证平台加速FPGA的开发:第三十一课——车牌识别的FPGA实现(3)车牌字符分割预处理
  • 从零开始学习Dify-Excel数据可视化(四)
  • PHP面向对象高级应用:依赖注入、服务容器与PSR标准实现
  • STL学习(四、队列和堆栈)
  • CSP-J系列【2023】P9751 [CSP-J 2023] 旅游巴士题解
  • 变频器实习DAY12
  • 接入海康设备mark全是false解决方案
  • Elasticsearch整合:Repository+RestClient双模式查询优化
  • 【杂谈】-代理协议:重塑AI协作新生态,开启智能互联新时代
  • 开闭原则在C++中的实现
  • InfluxDB HTTP API 接口调用详解(二)
  • [HarmonyOS] 鸿蒙LiteOS-A内核深度解析 —— 面向 IoT 与智能终端的“小而强大”内核
  • 算法第27天|贪心算法:合并区间 、单调递增的数字