3D 版接雨水

3D 版接雨水

这道题目在面试的时候比较热门,所以单拿出这道题目进行一次学习记录。原题的链接是:407. 接雨水 II。

题目描述

思路

其实和 2D 接雨水类似,3D 接雨水也要将每一个格子作为最终统计答案的单位,计算每一个格子存储最多的水时高度是多少。假如我们以water[i][j]来代表坐标点(i, j)位置最多可以存储的水的高度,那么最终这个单位对答案的贡献值就是water[i][j] - heightMap[i][j],此处的heightMap是题目的输入,记录的就是每一个单元格的高度。

这道题的解题思路参考自 LeetCode 官方的题解,使用广搜的思路来解决这个问题。

首先我们要做的就是找到heightMap当中的最高点maxHeight,并假设初始时所有的格子可存放的水的高度都是maxHeight。显然,这对于处在边界上的格子来说是不现实的,因为它们无法存储任何的水,所有的水都会从边界流下去。因此,在设置完water[i][j] = maxHeight之后,我们首先对边界的状态进行维护。

具体来说,我们需要判断的是,边界状态的water[i][j]是否高于heightMap[i][j],如果是的话,就说明这个格子存储的水溢出了,将状态修复为water[i][j] = heightMap[i][j],并将这个坐标存储到一个队列q当中,用于后续的广搜。

解决这道题目的关键就在于队列的使用,方才我们首先维护的是边界上格子的状态,这就意味着所有有水溢出的格子都将会对接下来中间格子的存储水的状态产生影响。

为了更好地理解上面这段话,我们可以看下面这张图:

如果此时存储水的方格边界上出现了一个缺口,那么以这个缺口为起点,其中所有可达的方格的状态都会产生改变。

将边界的状态存储到队列q当中之后,我们就可以开始进行广搜了。具体来说,每一次取队首的一个元素,它是一个二维坐标,我们要判断的是它周围的四个合法的(不超出边界)坐标是否需要进行维护。

是否需要进行维护,就需要我们进行进一步的判断了,这个判断条件也是解决这道题的关键。假设我们从队首取出的元素的坐标是(x, y),(nx, ny)是其一跳之内上下左右四个方向的新的合法位置。如果water[nx][ny] > water[x][y],就说明这个位置存储水的高度比当前位置还要高,如果同时满足water[nx][ny] > heightMap[nx][ny],就说明(nx, ny)这个位置的水溢出了。因为:

  • water[nx][ny] > water[x][y]代表(x, y)这个位置相邻的一个位置(nx, ny)的存水量比它高,假设heightMax[nx][ny],也就是(nx, ny)的墙体的高度比存水量要低,那么water[nx][ny]的水一定会流向water[x][y],进一步流向更低的位置,最终从边界流出去;
  • water[nx][ny] > heightMap[nx][ny]进一步说明(nx, ny)这个位置的存水量确实比墙体的高度还要高,如果water[nx][ny] == heightMap[nx][ny],就说明这个位置其实是一个墙体,它的存水量为water[nx][ny] - heightMap[nx][ny] == 0。

满足上述两个条件,就说明(nx, ny)这个位置的水存不住了,需要维护它的状态,也就是water[nx][ny] = max(water[x][y], heightMap[nx][ny])。状态维护的方程很好理解,要么这个位置的水位线与它的邻格(x, y)平齐,要么所有水全部流走了,水位线高度降至墙体的高度。同时将(nx, ny)这个位置加入到队列当中,用于对它的邻居的状态进行维护。

最后,基于water和heightMap两个二维数组统计最终的答案即可。

Golang 代码

go 复制代码
func trapRainWater(heightMap [][]int) int {
	// 首先找到最高的高度
	maxHeight := 0
	m, n := len(heightMap), len(heightMap[0])
	for i := 0; i < m; i ++ {
		for j := 0; j < n; j ++ {
			maxHeight = max(maxHeight, heightMap[i][j])
		}
	}

	// 之后初始化一个 water 二维数组, 记录每一个格子的水最高可以存多高
	water := make([][]int, m)
	for i := 0; i < m; i ++ {
		water[i] = make([]int, n)
		for j := 0; j < n; j ++ {
			// 初始化所有格子的高度为最高的 maxHeight
			water[i][j] = maxHeight
		}
	}

	type loc struct {
		x, y int
	}
	q := []loc{}    // 初始化一个队列, 存储的是格子的二维坐标

	for i := 0; i < m; i ++ {
		for j := 0; j < n; j ++ {
			if i == 0 || i == m - 1 || j == 0 || j == n - 1 {
				if water[i][j] > heightMap[i][j] {
					// 首先对边界上的格子进行维护, 再逐渐推到里面
					water[i][j] = heightMap[i][j]
					q = append(q, loc{i, j})
				}
			}
		}
	}

	// 维护一个队列, 如果某个格子的水的高度发生了变化, 应当告知周围的格子并改变状态
	dx, dy := []int{0, 0, 1, -1}, []int{1, -1, 0, 0}
	for len(q) > 0 {
		p := q[0]; q = q[1:]
		x, y := p.x, p.y
		for i := 0; i < 4; i ++ {
			nx, ny := x + dx[i], y + dy[i]
			if nx < 0 || nx >= m || ny < 0 || ny >= n {
				continue
			}
			if water[x][y] < water[nx][ny] && heightMap[nx][ny] < water[nx][ny] {
				water[nx][ny] = max(water[x][y], heightMap[nx][ny])
				q = append(q, loc{nx, ny})
			}
		}
	}

	// 统计答案
	ans := 0
	for i := 0; i < m; i ++ {
		for j := 0; j < n; j ++ {
			ans += water[i][j] - heightMap[i][j]
		}
	}

	return ans
}
相关推荐
紧跟先前的步伐12 小时前
Go AI Agent 实战 22|完善 Agent 执行循环:从工具调用到最终回答
人工智能·ai·golang
后台模板学习13 小时前
Go Web开发中任务调度框架的源码解析与高可用实现方案
开发语言·后端·golang
FungLeo13 小时前
成为全栈·Go 后端篇·分类树与标签关系:目录变更为何需要协调
golang·go·gorm·数据库事务·分类树·成为全栈
fly-iot14 小时前
【FDE知识库】(1):AI 编程时代开发语言选型分析,云使用go,企业用java,算法python,底层c
开发语言·人工智能·golang
FungLeo14 小时前
成为全栈·Go 后端篇·文章查询与展示投影:安全分页、排序和软删除
golang·go·gorm·分页查询·成为全栈·sql安全
紧跟先前的步伐1 天前
Go AI Agent 实战 24|工具系统工程化收尾与阶段验收
人工智能·ai·golang·1024程序员节
日日大王学编程1 天前
#Feed 流项目复盘:Redis 时间线缓存与 Outbox 消息可靠性设计
golang·1024程序员节
北冥you鱼1 天前
Go 语言 CRUD 实战:从接口设计到真实业务场景
开发语言·后端·golang
利威尔xu1 天前
Go IM 源码实战(11):token 只是个 HASH,多端互踢的顺序错了就踢错人
后端·微服务·面试·架构·golang
microrain1 天前
SagooIoT 把协议解析和消息通知都放进了独立进程
物联网·golang·插件·sagooiot