我好想逃却逃不掉
问题描述
曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……
对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。
——我好想逃。
但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!
在一个 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
题解
这个问题可以通过图遍历和拓扑排序的思路解决。
解题思路
-
构建图结构:
- 将竞技场地图中的每个位置视为一个图的节点。
- 如果是普通地板
.,该节点可以自由移动到上下左右相邻的节点。 - 如果是传送器
U,D,L,R,它会将当前节点强制连接到对应方向的相邻节点。
-
确定出度:
- 对于每个节点,我们计算出它的“出度”(从该节点能直接走到的其他节点数)。这在之后判断节点是否可以到达出口时会非常有用。
-
反向 BFS 处理能到达出口的节点:
- 从出口
O开始,用反向 BFS 找到所有可以到达出口的节点。对于每个节点,如果它指向的相邻节点已被标记为能到达出口,则该节点也能到达出口。 - 将所有可以到达出口的节点标记出来。
- 从出口
-
计数危险位置:
- 竞技场中未标记为可达出口的节点即为“危险位置”,统计这些位置的数量。
Java 实现
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class Main {
static class Position {
int row;
int col;
public Position(int row, int col) {
this.row = row;
this.col = col;
}
}
public static int solution(int N, int M, char[][] data) {
// Edit your code here
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
Map<Character, int[]> teleportMap = new HashMap<>();
teleportMap.put('U', new int[]{-1, 0});
teleportMap.put('D', new int[]{1, 0});
teleportMap.put('L', new int[]{0, -1});
teleportMap.put('R', new int[]{0, 1});
boolean[][] reachable = new boolean[N][M];
Queue<Position> queue = new LinkedList<>();
// 找出出口位置,并将其标记为可达
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (data[i][j] == 'O') {
queue.add(new Position(i, j));
reachable[i][j] = true;
}
}
}
// 反向BFS,标记所有可以到达出口的节点
while (!queue.isEmpty()) {
Position pos = queue.poll();
int r = pos.row;
int c = pos.col;
for (int[] dir : directions) {
int nr = r + dir[0];
int nc = c + dir[1];
if (nr >= 0 && nr < N && nc >= 0 && nc < M && data[nr][nc] == '.') {
if (!reachable[nr][nc]) {
reachable[nr][nc] = true;
queue.add(new Position(nr, nc));
}
}
}
// 处理传送器的反向标记
for (Map.Entry<Character, int[]> entry : teleportMap.entrySet()) {
int[] move = entry.getValue();
int pr = r - move[0];
int pc = c - move[1];
if (pr >= 0 && pr < N && pc >= 0 && pc < M && data[pr][pc] == entry.getKey() && !reachable[pr][pc]) {
reachable[pr][pc] = true;
queue.add(new Position(pr, pc));
}
}
}
// 统计不可达的位置
int dangerousPositions = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (!reachable[i][j]) {
dangerousPositions++;
}
}
}
return dangerousPositions;
}
public static void main(String[] args) {
// Add your test cases here
char[][] pattern = {
{'.', '.', '.', '.', '.'},
{'.', 'R', 'R', 'D', '.'},
{'.', 'U', '.', 'D', 'R'},
{'.', 'U', 'L', 'L', '.'},
{'.', '.', '.', '.', 'O'}
};
System.out.println(solution(5, 5, pattern) == 10);
}
}
解释
- 初始化:找到出口位置,并将其作为 BFS 的起点。
- 反向 BFS:从出口位置向各个方向传播,标记所有能够到达出口的节点。
- 传送器处理:对于每个传送器的反向位置进行检查,如果能从该位置通过传送器到达已标记的可达位置,则将其标记为可达。
- 统计危险位置:遍历
reachable数组,统计所有未被标记的节点即为“危险位置”。
复杂度分析
- 时间复杂度:O(N×M),遍历和标记每个位置最多一次。
- 空间复杂度:O(N×M),用于存储
reachable标记和 BFS 队列。