问题描述
曾经的我不过是一介草民,混迹市井,默默无名。直到我被罗马的士兵从家乡捉走丢进竞技场……
对手出现了,我架紧盾牌想要防御,只觉得巨大的冲击力有如一面城墙冲涌而来,击碎了我的盾牌,我两眼发昏,沉重的身躯轰然倒地。
——我好想逃。
但罗马最大的竞技场,哪有这么容易逃得掉。工程师们早就在地上装了传送机关,虽不会伤人,却会将站在上面的人传到它指向的位置。若是几个传送机关围成一个环,不小心踩在上面的人就会被一圈圈地反复传送……想到这里,我不由得打了个寒颤。必须避开这些危险的地方!
在一个 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
解题思路如下:
- 初始化参数:定义方向映射数组
<font style="color:rgb(26, 32, 41);">directions</font>
和方向变化数组<font style="color:rgb(26, 32, 41);">directionDeltas</font>
,这些将用于确定传送点的移动方向。 - 遍历迷宫:使用双层循环遍历迷宫的每个位置(除了出口本身)。对于每个位置,我们需要判断是否能够到达出口。
- 检查有效性:定义一个
<font style="color:rgb(26, 32, 41);">isValid</font>
方法来检查给定的坐标是否在迷宫的有效范围内。 - 深度优先搜索(DFS):定义一个
<font style="color:rgb(26, 32, 41);">canReachExit</font>
方法,使用递归的方式进行深度优先搜索,以检查从当前坐标是否可以到达出口。 - 处理传送点:如果当前坐标是传送点,根据传送点的方向直接移动到下一个位置,并继续递归检查。
- 探索普通点:如果当前坐标是普通点,尝试所有四个可能的方向(上、下、左、右),递归地检查每个方向是否可以到达出口。
- 记录访问状态:使用一个
<font style="color:rgb(26, 32, 41);">HashSet</font>
来记录已经访问过的位置,以避免重复访问和无限循环。 - 统计无法到达出口的位置:如果在递归过程中,从某个位置出发无法到达出口,则将该位置计入无法到达出口的位置总数。
- 返回结果:遍历完成后,返回无法到达出口的位置总数。
算法实现步骤:
- 遍历迷宫的每个位置。
- 对于每个位置,如果它不是出口,使用深度优先搜索(DFS)来检查是否可以从该位置到达出口。
- 使用一个集合来记录已经访问过的位置,以避免无限循环。
- 如果位置是传送点,直接按照传送点的方向移动到下一个位置,并继续检查。
- 如果位置是普通点,尝试所有四个方向(上、下、左、右)。
- 如果从某个位置出发无法到达出口,则计数器加一。
- 最后,返回计数器的值,即无法到达出口的位置数量。
import java.util.*;
public class Main {
// 方向映射
private final static String[] directions = { "U", "D", "L", "R" };
private final static int[][] directionDeltas = { { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 } };
public static int solution(int N, int M, char[][] data) {
// 统计无法到达出口的位置数量
int count = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (data[i][j] != 'O') { // 不考虑出口本身
if (!canReachExit(N, M, i, j, new HashSet<>(), data)) {
count++;
}
}
}
}
return count;
}
// 检查位置是否有效
public static boolean isValid(int N, int M, int x, int y) {
return x >= 0 && x < N && y >= 0 && y < M;
}
// 使用递归检查从给定位置是否可以到达出口
public static boolean canReachExit(int N, int M, int start_x, int start_y, Set<String> visited, char[][] data) {
if (!isValid(N, M, start_x, start_y)) {
return false;
}
String key = start_x + "," + start_y;
if (visited.contains(key)) {
return false;
}
if (data[start_x][start_y] == 'O') {
return true;
}
visited.add(key);
// 如果是传送点
for (int i = 0; i < directions.length; i++) {
if (data[start_x][start_y] == directions[i].charAt(0)) {
int dx = directionDeltas[i][0];
int dy = directionDeltas[i][1];
return canReachExit(N, M, start_x + dx, start_y + dy, visited, data);
}
}
// 如果是普通点,尝试四个方向
for (int[] delta : directionDeltas) {
int next_x = start_x + delta[0];
int next_y = start_y + delta[1];
if (isValid(N, M, next_x, next_y) && canReachExit(N, M, next_x, next_y, new HashSet<>(visited), data)) {
return true;
}
}
return false;
}
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);
}
}