问题描述
曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……
对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。
——我好想逃。
但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!
在一个 N × M 的竞技场迷宫中,你的任务是找出在迷宫中,所有"危险位置"的数量。 "危险位置"定义为:如果站在该位置上,无论采取什么移动策略,都无法到达出口。
竞技场中包含以下几种元素:
.:表示普通地板,可以自由移动到上下左右相邻的格子(不可以走斜线)O:表示出口U:表示向上的传送器,踩上去会被强制传送到上方的格子D:表示向下的传送器,踩上去会被强制传送到下方的格子L:表示向左的传送器,踩上去会被强制传送到左方的格子R:表示向右的传送器,踩上去会被强制传送到右方的格子
注意,如果被传送出了竞技场之外,则算作死亡。
输入参数
- N: 一个整数,表示竞技场地图的行数
- M: 一个整数,表示竞技场地图的列数
- data: 一个字符二维数组,表示竞技场地板地图。数组大小为 N × M,其中 1 ≤ N, M ≤ 100
测试样例
样例1:
输入:
N = 5, M = 5, data = [ [".", ".", ".", ".", "."], [".", "R", "R", "D", "."], [".", "U", ".", "D", "R"], [".", "U", "L", "L", "."], [".", ".", ".", ".", "O"] ]输出:10解释:存在 10 个位置,如果站在这些位置上,将永远无法到达右下角的出口(用 X 标记): ['.', '.', '.', '.', '.'] ['.', 'X', 'X', 'X', '.'] ['.', 'X', 'X', 'X', 'X'] ['.', 'X', 'X', 'X', '.'] ['.', '.', '.', '.', 'O']
样例2:
输入:
N = 4, M = 4, data = [[".", "R", ".", "O"], ["U", ".", "L", "."], [".", "D", ".", "."], [".", ".", "R", "D"]]输出:2
样例3:
输入:
N = 3, M = 3, data = [[".", "U", "O"], ["L", ".", "R"], ["D", ".", "."]]输出:8
解题思路可以分为以下几个步骤:
1. 寻找迷宫出口
首先,代码遍历整个迷宫(一个二维数组 data),找到表示出口的字符 'O' 的坐标 (exitX, exitY)。
- 为什么要找到出口? 出口是迷宫中最关键的位置,所有路径的可达性都基于从出口向外扩散的探索。
2. 深度优先搜索 (DFS) 标记可达路径
使用深度优先搜索(DFS)从出口出发,标记所有可以到达的迷宫位置:
-
基本规则
:
- 如果当前位置是出口,或通过其他路径可达,就继续探索周围四个方向(上、下、左、右)。
- 每个位置只能访问一次(用
visited数组记录访问状态)。 - 如果当前位置符合规则(比如周围的方向是合法的通路
'D', 'U', 'L', 'R', '.'),就递归地检查下一步。
-
特殊通路规则
: 迷宫中某些字符限制了移动方向:
'D'允许从当前位置向下移动。'U'允许从当前位置向上移动。'L'允许从当前位置向左移动。'R'允许从当前位置向右移动。'.'表示无方向限制,可以自由通过。
每次递归时都会检查这些条件,确保路径探索的正确性。
3. 标记可达位置
DFS 中的 canWalk 数组用于标记哪些位置是从出口可以到达的。
- 初始化时,所有位置默认不可达(
False),除了出口位置(True)。 - 每访问一个符合条件的格子,就将其标记为
True,并继续递归探索。
4. 统计不可达位置
在完成整个迷宫的 DFS 遍历后,代码再次遍历整个迷宫,通过检查 canWalk 数组中值为 False 的格子数量,统计 不可达位置 的总数。
关键逻辑的总结
- 出口是起点,从出口出发,反向确定所有可以到达的位置。
- 深度优先搜索模拟了人从出口开始“走迷宫”的过程,符合迷宫通行规则的路径会被标记为可达。
- 最终,通过对不可达位置数量的统计,得出答案。
代码的思维图解
以示例迷宫为例:
. . . . .
. R R D .
. U . D R
. U L L .
. . . . O
-
步骤 1:找到出口位置
(4, 4)。 -
步骤 2
:从出口开始进行 DFS:
- 出口位置
(4, 4)标记为可达。 - 检查
(4, 3)是否符合条件,发现它是'.',继续递归标记。 - 继续递归向出口四周扩展,直到所有可以到达的位置都被标记。
- 出口位置
-
步骤 3:统计
canWalk数组中标记为False的格子数量。结果为 10,即有 10 个位置无法到达出口。
复杂度分析
-
时间复杂度:
- 遍历迷宫寻找出口:
O(N * M)。 - 深度优先搜索:最多访问每个格子一次,因此时间复杂度为
O(N * M)。 - 统计不可达位置:
O(N * M)。 总计时间复杂度为O(N * M)。
- 遍历迷宫寻找出口:
-
空间复杂度:
- 需要额外的
visited和canWalk数组,大小均为N * M。 - 递归调用栈的深度最大为迷宫大小,空间复杂度为
O(N * M)。
- 需要额外的
代码实现
def solution(N, M, data):
def dfs(x, y, can_walk):
# 判断是否在迷宫内和访问过
if x < 0 or x >= N or y < 0 or y >= M: # 边界条件
return
if visited[x][y]: # 如果已经访问过
return
visited[x][y] = True
can_walk[x][y] = True # 标记当前位置为可到达
# 向上检查
if x - 1 >= 0 and (data[x - 1][y] == 'D' or data[x - 1][y] == '.'):
dfs(x - 1, y, can_walk)
# 向下检查
if x + 1 < N and (data[x + 1][y] == 'U' or data[x + 1][y] == '.'):
dfs(x + 1, y, can_walk)
# 向左检查
if y - 1 >= 0 and (data[x][y - 1] == 'R' or data[x][y - 1] == '.'):
dfs(x, y - 1, can_walk)
# 向右检查
if y + 1 < M and (data[x][y + 1] == 'L' or data[x][y + 1] == '.'):
dfs(x, y + 1, can_walk)
exit_x, exit_y = 0, 0
found_exit = False
# 找到迷宫出口
for i in range(N):
for j in range(M):
if data[i][j] == 'O':
exit_x, exit_y = i, j
found_exit = True
break
if found_exit:
break
visited = [[False] * M for _ in range(N)]
can_walk = [[False] * M for _ in range(N)]
can_walk[exit_x][exit_y] = True # 标记出口为可达
dfs(exit_x, exit_y, can_walk)
# 统计无法到达出口的位置数量
unreachable_count = 0
for i in range(N):
for j in range(M):
if not can_walk[i][j]:
unreachable_count += 1
return unreachable_count
if __name__ == "__main__":
pattern = [
['.', '.', '.', '.', '.'],
['.', 'R', 'R', 'D', '.'],
['.', 'U', '.', 'D', 'R'],
['.', 'U', 'L', 'L', '.'],
['.', '.', '.', '.', 'O']
]
print(solution(5, 5, pattern) == 10)