简单题28: 我好想逃却逃不掉

163 阅读6分钟

问题描述

曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……

对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。

——我好想逃。

但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!

在一个 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. 反向思考:从出口出发标记所有可以到达出口的“安全位置”。

  2. 广度优先搜索 (BFS) :用 BFS 搜索出口的所有可达位置,标记为“安全”。

  3. 反向传送:对传送器的方向进行反向操作。例如,U(向上传送器)意味着站在它上方的位置可以到达它,因此将其上方位置加入队列。

详细步骤

  1. 初始化安全标记数组:创建一个二维布尔数组 safe,初始时所有元素为 False,表示所有位置默认都是“危险位置”。

  2. 找到出口并初始化 BFS 队列

• 找到地图中的出口位置 O,并将其加入 BFS 队列作为起始点。

• 将出口位置在 safe 数组中标记为 True,表示这是安全的。

  1. 广度优先搜索

• 使用 BFS,从出口位置开始逐步标记所有安全位置。

普通地板移动:对每个普通地板位置,检查上下左右四个方向相邻的格子,如果相邻格子是未标记的普通地板,将其加入队列并标记为安全。

反向传送器处理:对于传送器位置,反向处理其传送方向。例如:

• 若当前位置是向上传送器 U,则检查上方位置并将其标记为安全。

• 若当前位置是向下传送器 D,则检查下方位置并将其标记为安全。

• 若当前位置是向左传送器 L,则检查左方位置并将其标记为安全。

• 若当前位置是向右传送器 R,则检查右方位置并将其标记为安全。

  1. 统计危险位置数量

• 遍历 safe 数组,未被标记为 True 的位置即为危险位置,统计其数量。

案例推导:示例1
输入地图:

N = 5, M = 5
data = [
    [".", ".", ".", ".", "."],
    [".", "R", "R", "D", "."],
    [".", "U", ".", "D", "R"],
    [".", "U", "L", "L", "."],
    [".", ".", ".", ".", "O"]
]

推导过程

  1. 初始化

• safe 数组创建完成,所有位置默认标记为 False。

• 出口位置 (4, 4) 发现,加入队列,并标记 safe[4][4] = True。

  1. 第一次 BFS 迭代

• 从出口 (4, 4) 出发,检查四个方向的普通地板,发现 (4, 3) 是普通地板,标记 safe[4][3] = True,加入队列。

  1. 第二次 BFS 迭代

• 从 (4, 3) 出发,检查 (4, 2) 普通地板,标记 safe[4][2] = True,加入队列。

• 检查上方 (3, 3) 位置,发现是向左传送器 L,因此上方的 (3, 3) 加入队列,标记为安全。

  1. 依次继续 BFS 迭代

• 处理普通地板 (4, 2)。

• 处理传送器 (3, 3) 的反向路径,继续传送至安全位置。

  1. 最终结果

• 在整个地图上,所有可达出口的安全位置已标记,未被标记的位置为危险位置,共 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