
专栏导读
本专栏收录于《华为OD机试真题(Python/JS/C/C++)》。
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。
一、题目描述
一个封闭迷宫,有 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)。
六、Python算法源码
from collections import deque
import sys
def min_meet_rounds(n, graph, start_a, path):
"""
将 B 的单程巡逻路径展开成完整周期。
例如:
[0, 1, 2] -> [0, 1, 2, 1]
循环后自然再次回到 0。
"""
if len(path) == 1:
cycle = path[:]
else:
cycle = path + path[-2:0:-1]
# 题目保证二者起点不同,这里做兼容处理。
if start_a == cycle[0]:
return 0
period = len(cycle)
/*
Python 不支持这种注释语法,下面使用 #。
*/
# visited[A所在房间][B的周期下标]
# 同一个 A 房间在 B 不同巡逻阶段下必须视为不同状态。
visited = [[False] * period for _ in range(n)]
# 队列元素:
# (A所在房间, B当前周期下标, 已经过的回合数)
queue = deque([(start_a, 0, 0)])
visited[start_a][0] = True
while queue:
a_room, phase, rounds = queue.popleft()
# 下一回合结束时 B 所在的位置。
next_phase = (phase + 1) % period
b_next_room = cycle[next_phase]
next_rounds = rounds + 1
# A 可以选择:
# 1. 停留在当前房间
# 2. 移动到任意相邻房间
possible_rooms = [a_room] + graph[a_room]
for next_a in possible_rooms:
# 每回合结束后判断是否相遇。
if next_a == b_next_room:
return next_rounds
if not visited[next_a][next_phase]:
visited[next_a][next_phase] = True
queue.append((
next_a,
next_phase,
next_rounds
))
# 有限状态全部访问仍没有相遇,之后只会重复。
return -1
def main():
lines = sys.stdin.read().splitlines()
n = int(lines[0].strip())
edges_line = lines[1].strip()
start_a = int(lines[2].strip())
path = [
int(x.strip())
for x in lines[3].strip().split(',')
]
# 邻接表保存无向图。
graph = [[] for _ in range(n)]
# 兼容空行、[]、Q。
if (
edges_line
and edges_line != '[]'
and edges_line.upper() != 'Q'
):
for token in edges_line.split():
u, v = map(int, token.split(','))
graph[u].append(v)
graph[v].append(u)
print(min_meet_rounds(
n,
graph,
start_a,
path
))
if __name__ == '__main__':
main()
七、JavaScript算法源码
const fs = require('fs');
const lines = fs.readFileSync(0, 'utf8').split(/\r?\n/);
const n = Number(lines[0].trim());
const edgesLine = (lines[1] || '').trim();
const startA = Number(lines[2].trim());
const path = lines[3]
.trim()
.split(',')
.map(s => Number(s.trim()));
// 使用邻接表保存无向图。
const graph = Array.from(
{ length: n },
() => []
);
// 兼容空行、[] 和 Q。
if (
edgesLine
&& edgesLine !== '[]'
&& edgesLine.toUpperCase() !== 'Q'
) {
for (const token of edgesLine.split(/\s+/)) {
const [u, v] = token
.split(',')
.map(Number);
graph[u].push(v);
graph[v].push(u);
}
}
function minMeetRounds(n, graph, startA, path) {
/*
* 构造 B 的完整巡逻周期。
*
* [0,1,2]
* =>
* [0,1,2,1]
*
* 周期结束后重新回到 0。
*/
let cycle;
if (path.length === 1) {
cycle = [path[0]];
} else {
cycle = [...path];
// 端点不能重复,否则就相当于 B 在端点多停一回合。
for (let i = path.length - 2; i >= 1; i--) {
cycle.push(path[i]);
}
}
if (startA === cycle[0]) {
return 0;
}
const period = cycle.length;
/*
* visited[A房间][B巡逻阶段]
*
* A 在同一房间、但 B 的巡逻阶段不同,
* 必须认为是两个不同的 BFS 状态。
*/
const visited = Array.from(
{ length: n },
() => Array(period).fill(false)
);
/*
* queue 中保存:
* [A房间, B周期下标, 已经过回合数]
*
* 使用 head 下标而不是 shift(),
* 可以避免数组频繁整体移动。
*/
const queue = [[startA, 0, 0]];
let head = 0;
visited[startA][0] = true;
while (head < queue.length) {
const [aRoom, phase, rounds] = queue[head++];
const nextPhase =
(phase + 1) % period;
const bNextRoom =
cycle[nextPhase];
const nextRounds =
rounds + 1;
/*
* 情况1:
* A 原地不动。
*/
if (aRoom === bNextRoom) {
return nextRounds;
}
if (!visited[aRoom][nextPhase]) {
visited[aRoom][nextPhase] = true;
queue.push([
aRoom,
nextPhase,
nextRounds
]);
}
/*
* 情况2:
* A 移动到相邻房间。
*/
for (const nextA of graph[aRoom]) {
if (nextA === bNextRoom) {
return nextRounds;
}
if (!visited[nextA][nextPhase]) {
visited[nextA][nextPhase] = true;
queue.push([
nextA,
nextPhase,
nextRounds
]);
}
}
}
return -1;
}
console.log(
minMeetRounds(
n,
graph,
startA,
path
)
);
八、C算法源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAX_N 100
#define MAX_LINE 10000
#define MAX_PATH 5000
/*
* n < 100,因此 C 版本直接使用邻接矩阵保存房间连接关系。
* graph[u][v] = 1 表示 u 与 v 之间存在双向门。
*/
static int graph[MAX_N][MAX_N];
static int minMeetRounds(
int n,
int startA,
const int *path,
int pathLen) {
int period;
if (pathLen == 1) {
period = 1;
} else {
period = 2 * (pathLen - 1);
}
int *cycle =
(int *)malloc(sizeof(int) * period);
int index = 0;
/*
* 构造 B 的完整往返巡逻周期。
*
* [0,1,2] -> [0,1,2,1]
*/
for (int i = 0; i < pathLen; ++i) {
cycle[index++] = path[i];
}
/*
* 反向路径只加入中间节点,
* 避免 B 在端点多停一回合。
*/
for (int i = pathLen - 2; i >= 1; --i) {
cycle[index++] = path[i];
}
if (startA == cycle[0]) {
free(cycle);
return 0;
}
/*
* 一共最多有 n * period 个 BFS 状态。
*
* 将二维状态
* (A房间, B周期下标)
*
* 编码为:
*
* state = A房间 * period + B周期下标
*/
int totalStates = n * period;
int *dist =
(int *)malloc(sizeof(int) * totalStates);
int *queue =
(int *)malloc(sizeof(int) * totalStates);
for (int i = 0; i < totalStates; ++i) {
dist[i] = -1;
}
int head = 0;
int tail = 0;
int startState = startA * period;
queue[tail++] = startState;
dist[startState] = 0;
while (head < tail) {
int state = queue[head++];
int aRoom = state / period;
int phase = state % period;
int rounds = dist[state];
/*
* 下一回合 B 一定移动到这个周期位置。
*/
int nextPhase =
(phase + 1) % period;
int bNextRoom =
cycle[nextPhase];
int nextRounds =
rounds + 1;
/*
* 情况1:
* A 停留在原房间。
*/
if (aRoom == bNextRoom) {
free(cycle);
free(dist);
free(queue);
return nextRounds;
}
int stayState =
aRoom * period + nextPhase;
if (dist[stayState] == -1) {
dist[stayState] =
nextRounds;
queue[tail++] =
stayState;
}
/*
* 情况2:
* A 移动到任意相邻房间。
*/
for (int nextA = 0; nextA < n; ++nextA) {
if (!graph[aRoom][nextA]) {
continue;
}
/*
* 只判断回合结束时的位置是否相同。
*/
if (nextA == bNextRoom) {
free(cycle);
free(dist);
free(queue);
return nextRounds;
}
int nextState =
nextA * period + nextPhase;
if (dist[nextState] == -1) {
dist[nextState] =
nextRounds;
queue[tail++] =
nextState;
}
}
}
free(cycle);
free(dist);
free(queue);
return -1;
}
int main(void) {
char line[MAX_LINE];
int n;
int startA;
int path[MAX_PATH];
int pathLen = 0;
/* 第一行:n */
fgets(line, sizeof(line), stdin);
n = atoi(line);
/* 第二行:edges */
fgets(line, sizeof(line), stdin);
char edgesLine[MAX_LINE];
strcpy(edgesLine, line);
/* 第三行:startA */
fgets(line, sizeof(line), stdin);
startA = atoi(line);
/* 第四行:patrolPath */
fgets(line, sizeof(line), stdin);
char *token =
strtok(line, ",\r\n");
while (token != NULL) {
path[pathLen++] =
atoi(token);
token =
strtok(NULL, ",\r\n");
}
/*
* 解析 edges。
* 兼容空行、[]、Q。
*/
char *p = edgesLine;
while (isspace((unsigned char)*p)) {
++p;
}
if (
*p != '\0'
&& *p != '\n'
&& strncmp(p, "[]", 2) != 0
&& toupper((unsigned char)*p) != 'Q'
) {
token =
strtok(p, " \t\r\n");
while (token != NULL) {
int u;
int v;
if (
sscanf(
token,
"%d,%d",
&u,
&v
) == 2
) {
// 双向门。
graph[u][v] = 1;
graph[v][u] = 1;
}
token =
strtok(NULL, " \t\r\n");
}
}
printf(
"%d\n",
minMeetRounds(
n,
startA,
path,
pathLen
)
);
return 0;
}
九、C++算法源码
#include <iostream>
#include <vector>
#include <queue>
#include <sstream>
#include <string>
#include <tuple>
using namespace std;
int minMeetRounds(
int n,
const vector<vector<int>>& graph,
int startA,
const vector<int>& path) {
/*
* 先保存正向路径。
*/
vector<int> cycle = path;
/*
* 将单程巡逻路径展开成完整往返周期。
*
* [0,1,2]
* =>
* [0,1,2,1]
*
* 不重复加入两个端点,
* 因为 B 到达端点后立即折返。
*/
if (path.size() > 1) {
for (
int i =
static_cast<int>(path.size()) - 2;
i >= 1;
--i
) {
cycle.push_back(path[i]);
}
}
if (startA == cycle[0]) {
return 0;
}
int period =
static_cast<int>(cycle.size());
/*
* visited[A当前房间][B巡逻周期下标]
*
* 二者共同确定完整状态。
*/
vector<vector<bool>> visited(
n,
vector<bool>(period, false)
);
/*
* 队列状态:
*
* A 当前房间
* B 当前周期下标
* 已经过回合数
*/
queue<tuple<int, int, int>> q;
q.emplace(
startA,
0,
0
);
visited[startA][0] = true;
while (!q.empty()) {
auto [aRoom, phase, rounds] =
q.front();
q.pop();
/*
* B 下一回合的位置完全确定。
*/
int nextPhase =
(phase + 1) % period;
int bNextRoom =
cycle[nextPhase];
int nextRounds =
rounds + 1;
/*
* 情况1:
* A 停留在原房间。
*/
if (aRoom == bNextRoom) {
return nextRounds;
}
if (!visited[aRoom][nextPhase]) {
visited[aRoom][nextPhase] =
true;
q.emplace(
aRoom,
nextPhase,
nextRounds
);
}
/*
* 情况2:
* A 移动到任意相邻房间。
*/
for (int nextA : graph[aRoom]) {
// 每回合结束后判断是否同房。
if (nextA == bNextRoom) {
return nextRounds;
}
if (!visited[nextA][nextPhase]) {
visited[nextA][nextPhase] =
true;
q.emplace(
nextA,
nextPhase,
nextRounds
);
}
}
}
/*
* 有限状态全部遍历后仍然没有相遇。
*/
return -1;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
string line;
/* 第一行:n */
getline(cin, line);
int n = stoi(line);
/* 第二行:edges */
string edgesLine;
getline(cin, edgesLine);
/* 第三行:startA */
getline(cin, line);
int startA = stoi(line);
/* 第四行:patrolPath */
string patrolLine;
getline(cin, patrolLine);
vector<vector<int>> graph(n);
/*
* 解析无向边。
* 兼容 [] 和 Q。
* 空行自然不会进入这里。
*/
if (
!edgesLine.empty()
&& edgesLine != "[]"
&& edgesLine != "Q"
&& edgesLine != "q"
) {
stringstream ss(edgesLine);
string token;
while (ss >> token) {
size_t comma =
token.find(',');
int u =
stoi(
token.substr(
0,
comma
)
);
int v =
stoi(
token.substr(
comma + 1
)
);
graph[u].push_back(v);
graph[v].push_back(u);
}
}
/*
* 解析 B 的单程路径。
*/
vector<int> path;
stringstream pathStream(
patrolLine
);
string token;
while (
getline(
pathStream,
token,
','
)
) {
path.push_back(
stoi(token)
);
}
cout
<< minMeetRounds(
n,
graph,
startA,
path
)
<< '\n';
return 0;
}
🏆下一篇:华为OD机试真题 - 简易内存池(Python/JS/C/C++ 新系统 200分)
🏆本文收录于,华为OD机试真题(Python/JS/C/C++)
刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。

273

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



