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 代码
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
}