题解:我好想逃却逃不掉
当初选择这道题主要是因为题目名称比较另类,就点进去看了看,关键点在于要仔细理解下传送带的机制,只要想到反向求可以到达的位置,就很容易联想到DFS了
题目理解
给定一个 N × M 的竞技场迷宫,目标是找到其中所有”危险位置”的数量。所谓”危险位置”,即如果站在该位置上,无论采取什么策略,都无法到达出口。迷宫的元素包括普通地板、出口、以及各种方向的传送器。
• 普通地板 (’.’) :可以自由移动到相邻的上下左右格子。
• 出口 (‘O’) :目标位置,任何能够到达此位置的地方都可以算作安全位置。
• 传送器 (‘U’, ‘D’, ‘L’, ‘R’) :踩上后会被强制传送到相应方向的相邻格子。传送器的特殊性质使得迷宫的探索变得复杂。
解题思路
为了找出所有的”危险位置”,我们首先需要确定哪些位置是安全的。安全的位置是指能够到达出口的位置。通过深度优先搜索(DFS)可以找到所有能够到达出口的区域,然后我们再反推哪些位置是无法到达出口的,也即是”危险位置”。
思路步骤:
-
找到出口位置: 先遍历迷宫,找到出口的位置。
-
从出口开始深度优先搜索: 从出口出发,利用 DFS 遍历所有可以到达的位置,将它们标记为安全的。
-
计算危险位置数量: 迷宫中所有没有被标记为安全的位置,都是”危险位置”。
DFS 详细实现
我们使用 DFS 从出口开始向四个方向探索,逐步标记可达的区域。传送器的处理需要特别注意:
• 如果踩上’R’,会被强制传送到右边的格子;
• 如果踩上’U’,会被强制传送到上方的格子;
• 如果踩上’D’,会被强制传送到下方的格子;
• 如果踩上’L’,会被强制传送到左边的格子。
DFS 的过程:
-
对于每一个位置,首先判断是否越界,如果越界则直接返回。
-
判断当前位置是否已经访问过,如果已经访问则跳过。
-
判断当前位置的内容,若是’出口’,则返回,不再继续探索。
-
对于普通地板和传送器,根据其方向执行相应的 DFS。
代码实现
package main
import (
"fmt"
)
// 不考虑传送带 修改可达位置为X
func dfs(i, j int, data [][]rune) int {
// 修改当前位置
data[i][j] = 'X'
sum := 1
// 试图往上走
if i - 1 >= 0 && (data[i - 1][j] == 'D' || data[i - 1][j] == '.'){
sum += dfs(i - 1, j, data)
}
// 试图往下走
if i + 1 < len(data) && (data[i + 1][j] == 'U' || data[i + 1][j] == '.'){
sum += dfs(i + 1, j ,data)
}
// 试图往左走
if j - 1 >= 0 && (data[i][j - 1] == 'R' || data[i][j - 1] == '.'){
sum += dfs(i, j - 1,data)
}
// 试图往右走
if j + 1 < len(data[0]) && (data[i][j + 1] == 'L' || data[i][j + 1] == '.'){
sum += dfs(i, j + 1,data)
}
return sum
}
func solution(N, M int, data [][]rune) int {
exitI := 0
exitJ := 0
n := len(data)
m := len(data[0])
// 找出口
for i := 0; i < n; i++ {
for j := 0; j < m; j++ {
if data[i][j] == 'O' {
// 找到出口
exitI = i
exitJ = j
}
}
}
x := dfs(exitI, exitJ, data)
return n * m - x
}
func main() {
pattern1 := [][]rune{
{'.', '.', '.', '.', '.'},
{'.', 'R', 'R', 'D', '.'},
{'.', 'U', '.', 'D', 'R'},
{'.', 'U', 'L', 'L', '.'},
{'.', '.', '.', '.', 'O'},
}
pattern2 := [][]rune{
{'.', '.', 'R', '.', '.'},
{'.', '.', 'R', '.', '.'},
{'.', '.', 'R', '.', '.'},
{'.', '.', 'D', 'U', 'U'},
{'.', '.', '.', '.', 'O'},
}
pattern3 := [][]rune{
{'.', 'D', 'L', 'L', '.'},
{'.', 'D', '.', 'U', '.'},
{'.', 'D', 'O', 'U', '.'},
{'.', 'D', '.', 'U', '.'},
{'.', 'R', 'R', 'U', '.'},
}
fmt.Println(solution(5, 5, pattern1) == 10)
fmt.Println(solution(5, 5, pattern2) == 11)
fmt.Println(solution(5, 5, pattern3) == 22)
}
关键点说明:
-
DFS遍历: 我们从出口出发,遍历所有可以到达的区域,每遍历一个位置就将其标记为已经访问(修改为’X’)。
-
如何处理传送器: 传送器并不会产生死循环,因为我们从出口出发,且每个传送器只能将你传送到相邻的一个格子。在 DFS 过程中,传送器本质上就是一种强制移动。
-
计算”危险位置”: DFS 完成后,所有没有被访问到的位置就可以认为是”危险位置”。
时间复杂度
• 时间复杂度: O(N * M),其中 N 和 M 分别是迷宫的行数和列数。因为我们最多会访问每个位置一次。
• 空间复杂度: O(N * M),用于存储迷宫的状态(每个位置的状态),即使在 DFS 中不做额外的存储,递归深度也最大为 N * M,因此空间复杂度是 O(N * M)。