问题描述
曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……
对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。
——我好想逃。
但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!
在一个 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
题目分析
竞技场逃生问题的目标是找出“危险位置”,即无论采取怎样的移动都无法到达出口的单元格。因为有传送器,部分单元格会被强制传送到特定方向的相邻单元格,从而可能形成循环导致无法逃离。
关键思路:
-
反向思考:从出口出发标记所有可以到达出口的“安全位置”。
-
广度优先搜索 (BFS) :用 BFS 搜索出口的所有可达位置,标记为“安全”。
-
反向传送:对传送器的方向进行反向操作。例如,U(向上传送器)意味着站在它上方的位置可以到达它,因此将其上方位置加入队列。
详细步骤
-
初始化安全标记数组:创建一个二维布尔数组 safe,初始时所有元素为 False,表示所有位置默认都是“危险位置”。
-
找到出口并初始化 BFS 队列:
• 找到地图中的出口位置 O,并将其加入 BFS 队列作为起始点。
• 将出口位置在 safe 数组中标记为 True,表示这是安全的。
- 广度优先搜索:
• 使用 BFS,从出口位置开始逐步标记所有安全位置。
• 普通地板移动:对每个普通地板位置,检查上下左右四个方向相邻的格子,如果相邻格子是未标记的普通地板,将其加入队列并标记为安全。
• 反向传送器处理:对于传送器位置,反向处理其传送方向。例如:
• 若当前位置是向上传送器 U,则检查上方位置并将其标记为安全。
• 若当前位置是向下传送器 D,则检查下方位置并将其标记为安全。
• 若当前位置是向左传送器 L,则检查左方位置并将其标记为安全。
• 若当前位置是向右传送器 R,则检查右方位置并将其标记为安全。
- 统计危险位置数量:
• 遍历 safe 数组,未被标记为 True 的位置即为危险位置,统计其数量。
案例推导:示例1
输入地图:
N = 5, M = 5
data = [
[".", ".", ".", ".", "."],
[".", "R", "R", "D", "."],
[".", "U", ".", "D", "R"],
[".", "U", "L", "L", "."],
[".", ".", ".", ".", "O"]
]
推导过程
- 初始化:
• safe 数组创建完成,所有位置默认标记为 False。
• 出口位置 (4, 4) 发现,加入队列,并标记 safe[4][4] = True。
- 第一次 BFS 迭代:
• 从出口 (4, 4) 出发,检查四个方向的普通地板,发现 (4, 3) 是普通地板,标记 safe[4][3] = True,加入队列。
- 第二次 BFS 迭代:
• 从 (4, 3) 出发,检查 (4, 2) 普通地板,标记 safe[4][2] = True,加入队列。
• 检查上方 (3, 3) 位置,发现是向左传送器 L,因此上方的 (3, 3) 加入队列,标记为安全。
- 依次继续 BFS 迭代:
• 处理普通地板 (4, 2)。
• 处理传送器 (3, 3) 的反向路径,继续传送至安全位置。
- 最终结果:
• 在整个地图上,所有可达出口的安全位置已标记,未被标记的位置为危险位置,共 10 个。
最终输出为 10。
def solution(N, M, data):
# 初始化访问数组,所有位置初始为未访问
safe = [[False] * M for _ in range(N)]
directions = {'U': (-1, 0), 'D': (1, 0), 'L': (0, -1), 'R': (0, 1)}
queue = []
# 找到出口位置并初始化队列
for i in range(N):
for j in range(M):
if data[i][j] == 'O': # 将出口加入队列
queue.append((i, j))
safe[i][j] = True # 标记出口为安全
# 广度优先搜索 (BFS) 从出口出发标记安全区域
while queue:
x, y = queue.pop(0)
# 判断普通地板的四个方向
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
nx, ny = x + dx, y + dy
if 0 <= nx < N and 0 <= ny < M and data[nx][ny] == '.' and not safe[nx][ny]:
# 普通地板可以自由移动
queue.append((nx, ny))
safe[nx][ny] = True
# 处理传送器的反向路径
for dir_key, (dx, dy) in directions.items():
# 计算反向传送的位置
rx, ry = x - dx, y - dy
if 0 <= rx < N and 0 <= ry < M and data[rx][ry] == dir_key and not safe[rx][ry]:
queue.append((rx, ry))
safe[rx][ry] = True
# 统计所有危险位置数量
dangerous_count = sum(1 for i in range(N) for j in range(M) if not safe[i][j] and data[i][j] != 'O')
return dangerous_count
if __name__ == "__main__":
pattern = [
[".", ".", ".", ".", "."],
[".", "R", "R", "D", "."],
[".", "U", ".", "D", "R"],
[".", "U", "L", "L", "."],
[".", ".", ".", ".", "O"]
]
print(solution(5, 5, pattern) == 10) # True