
华为OD机试 新系统 题库疯狂收录中,刷题点这里
专栏导读
本专栏收录于《华为OD机试(JAVA)真题》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。
一、题目描述
一个封闭迷宫,有 n 个房间(编号 0 到 n − 1),相邻房间由双向门连接。玩家 A 自由移动,玩家 B 沿给定路径来回巡逻。求玩家 A 的最优移动策略,使得与玩家 B 在最少回合相遇。
注意: A 和 B 初始出发点不同。
规则:
-
每回合,A 可以移动到相邻房间或停留在当前房间
-
B 按给定路径循环移动:从起点开始,每回合移动到路径下一个位置,到达末端后折返,回到起点后再折返,如此循环往复。间内无需停留,如:单程 [0,1,2],完整路径为 [0,1,2,1,0,1,…]。初始在 0 点,第一回合进到 1…
-
A 和 B 某回合结束后在同一房间,视为相遇
二、输入描述
第一行: n 房间数量 1 < n < 100
第二行:edges 房间连接关系,例如 0,1 1,2 2,3 3,4,双向连通,空数组 Q 表示无元连接
第三行: startA A 的初始房间编号
第四行: patrolPath B 的巡逻路径(一次单程),例如 0,1,2 表示 B 从 0 出发,经过 1 到达 2,然后原路返回,路径为 0,1,2,1,0,1,2,.....。至少有一个元素,表示 B 原地不动
三、输出描述
A 和 B 相遇的最小回合次数,若无法相遇返回 −1
四、测试用例
测试用例1:
1、输入
5
0,1 1,2 2,3 3,4
4
0,1,2,3,4
2、输出
2
3、说明
第 1 回合:
A: 4 -> 3
B: 0 -> 1
第 2 回合:
A: 3 -> 2
B: 1 -> 2
因此答案为 2。
测试用例2:
1、输入
8
0,1 1,2 2,3 3,4 4,5 5,6 6,7 7,5
0
5,6,7,5
2、输出
6
3、说明
B 的完整周期为:
5,6,7,5,7,6
之后重新循环到 5。
一种最优策略为:
回合0:A=0,B=5
回合1:A=0,B=6
回合2:A=1,B=7
回合3:A=2,B=5
回合4:A=3,B=7
回合5:A=4,B=6
回合6:A=5,B=5
所以最少 6 回合。
五、解题思路
B 的移动是完全确定且周期性的,因此问题不能只记录 A 当前在哪个房间,还必须记录“当前 B 走到了巡逻周期的哪个阶段”。
首先把 B 给出的单程路径转换成完整往返周期。例如单程路径 [0,1,2],实际周期可表示为 [0,1,2,1],之后再次循环到 0;如果路径只有一个房间,则 B 永远停在该房间,周期长度为 1。
接下来使用 BFS。状态定义为:
(A 当前房间, B 当前巡逻周期下标)
假设当前状态是 (a, phase),进入下一回合后,B 一定移动到
cycle[(phase + 1) % period]。
与此同时,A 有两类选择:停留在房间 a,或者移动到 graph[a] 中任意相邻房间。对于 A 的每一个下一位置,都和 B 本回合结束后的房间比较;如果相同,就立即返回当前回合数 + 1。
由于 BFS 按回合数逐层扩展,所以第一次找到相遇状态时,一定对应最少回合。
visited 不能只记录 A 的房间,因为 A 即使再次到达同一房间,只要 B 所处的巡逻阶段不同,后续结果就可能完全不同。因此访问标记必须是 visited[room][phase]。
整个状态空间是有限的,最多只有 n × period 个状态。如果所有状态都访问完仍没有相遇,则以后只会重复之前的状态,所以可以确定永远无法相遇,返回 -1。
设 B 单程路径长度为 m,则周期 P=1(m=1)或 P=2(m-1)。时间复杂度约为 O(P×(n+E)),空间复杂度为 O(n×P+E)。
六、Java算法源码
public class OdTest {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = Integer.parseInt(scanner.nextLine().trim());
String edgesLine = scanner.nextLine().trim();
int startA = Integer.parseInt(scanner.nextLine().trim());
String patrolLine = scanner.nextLine().trim();
// 使用邻接表保存迷宫的无向图结构。
// 相比邻接矩阵,在边数量较少时可以减少不必要的遍历。
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < n; i++) {
graph.add(new ArrayList<>());
}
// 兼容空行、[]、Q 三种没有房间连接关系的写法。
if (!edgesLine.isEmpty()
&& !edgesLine.equals("[]")
&& !edgesLine.equalsIgnoreCase("Q")) {
String[] edgeTokens = edgesLine.split("\\s+");
for (String token : edgeTokens) {
String[] uv = token.split(",");
int u = Integer.parseInt(uv[0].trim());
int v = Integer.parseInt(uv[1].trim());
// 房间之间是双向门,因此两个方向都加入邻接表。
graph.get(u).add(v);
graph.get(v).add(u);
}
}
// 解析 B 的单程巡逻路径。
String[] pathTokens = patrolLine.split(",");
int[] path = new int[pathTokens.length];
for (int i = 0; i < pathTokens.length; i++) {
path[i] = Integer.parseInt(pathTokens[i].trim());
}
System.out.println(minMeetRounds(n, graph, startA, path));
scanner.close();
}
private static int minMeetRounds(
int n,
List<List<Integer>> graph,
int startA,
int[] path) {
/*
* 将 B 的单程路径转换成完整的往返周期。
*
* 例如:
* path = [0,1,2]
*
* B 的移动为:
* 0 -> 1 -> 2 -> 1 -> 0 -> 1 ...
*
* 因此只需要保存一个周期:
* [0,1,2,1]
*
* 数组循环后会自然从 1 回到 0。
*/
int[] cycle;
if (path.length == 1) {
// 只有一个房间时,B 始终原地不动。
cycle = new int[]{path[0]};
} else {
int period = 2 * (path.length - 1);
cycle = new int[period];
int index = 0;
// 正向路径。
for (int room : path) {
cycle[index++] = room;
}
/*
* 反向加入中间节点。
*
* 起点和终点不能重复加入,
* 因为 B 到达端点后会立即折返,不会额外停留一回合。
*/
for (int i = path.length - 2; i >= 1; i--) {
cycle[index++] = path[i];
}
}
// 题目保证起点不同,这里仍做兼容处理。
if (startA == cycle[0]) {
return 0;
}
int period = cycle.length;
/*
* BFS 状态:
*
* (A 当前房间, B 当前所处的巡逻周期位置)
*
* visited[a][phase] 表示这个组合状态是否已经访问过。
*
* 不能只记录 A 所在的房间,因为即使 A 在同一个房间,
* B 处于不同巡逻阶段时,后面的结果也完全可能不同。
*/
boolean[][] visited = new boolean[n][period];
ArrayDeque<int[]> queue = new ArrayDeque<>();
// state[0] = A 所在房间
// state[1] = B 当前周期下标
// state[2] = 已经过的回合数
queue.offer(new int[]{startA, 0, 0});
visited[startA][0] = true;
while (!queue.isEmpty()) {
int[] state = queue.poll();
int aRoom = state[0];
int phase = state[1];
int rounds = state[2];
/*
* 进入下一回合以后,
* B 的位置是完全确定的。
*/
int nextPhase = (phase + 1) % period;
int bNextRoom = cycle[nextPhase];
int nextRounds = rounds + 1;
/*
* 情况 1:
* A 本回合选择停留在原房间。
*
* 注意题目规定是在“回合结束后”判断是否相遇,
* 所以应该与 B 移动后的房间进行比较。
*/
if (aRoom == bNextRoom) {
return nextRounds;
}
if (!visited[aRoom][nextPhase]) {
visited[aRoom][nextPhase] = true;
queue.offer(new int[]{
aRoom,
nextPhase,
nextRounds
});
}
/*
* 情况 2:
* A 移动到任意一个相邻房间。
*/
for (int nextARoom : graph.get(aRoom)) {
// 回合结束时两人位于同一个房间。
if (nextARoom == bNextRoom) {
return nextRounds;
}
if (!visited[nextARoom][nextPhase]) {
visited[nextARoom][nextPhase] = true;
queue.offer(new int[]{
nextARoom,
nextPhase,
nextRounds
});
}
}
}
/*
* 所有 “A房间 × B周期位置” 状态都搜索过仍无法相遇。
* 后面只会重复这些状态,因此一定永远无法相遇。
*/
return -1;
}
}
七、效果展示
1、输入
4
0,1 1,2 2,3
3
0
2、输出
3
3、说明
B 始终位于房间 0,A 的路线:
3 -> 2 -> 1 -> 0
需要 3 回合。

🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 新系统 200分)
🏆本专栏收录于《华为OD机试(JAVA)真题》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天CSDN在线答疑。

7万+

被折叠的 条评论
为什么被折叠?



