题目链接
塔子哥的codeFun2000:http://101.43.147.120/p/P1251
测试样例1
输入
3
2
1 0 1 2
2 1 2 0
100 100 100
100 000 100
000 000 001
输出
1
测试样例2
输入
3
2
1 0 2 0
0 1 2 2
000 000 001
010 101 101
110 010 000
输出
5
解释:最快的移动顺序: [2,2] ->[1,2] ->[2,2] ->[2,1] ->[1,1] ->[0,1]
思路
本题属于迷宫类最短路径的问题,首先想到的是BFS,但与常规的迷宫问题不同,该题目中迷宫地图为动态的,障碍物(墙壁)会发生改变,其变化存在周期性(周期为3,对应三个时间点的地图)。且题目中冒险家可选择原地不动,即每次移动有上、下、左、右、原地不动共5种情况。另外,题目中需求解最短时间与常规BFS求解最短路径长度没有本质区别。
综上思考,结合原题,代码思路总结为以下几点:
- 直接套用BFS板子。
- 将迷宫存储为动态,用三维邻接矩阵存储(第一维为时间);同时地图中还有不随时间改变的陷阱,即存在动态障碍和静态障碍。
- 每次移动共有5种情况。
- 在常规BFS中的用于记录(x,y)是否走过的
boolearn visited
数组,在本题中改为三维,即对应三个时间点的某个位置(x,y)是否走过。 - BFS的队列中存储每个点及到达该点的时间,每个点为(x,y,t)。
- 因地图以三个单位时间循环,也就说如果地图中的所有可达点对应的三个时刻均被走过,则题目无解。
BFS板子
BFS模板:While循环+队列
分支限界法(BFS、广度优先搜索):每一个活结点只有一次机会成为扩展结点,一旦活结点成为了扩展结点,就一次性产生其所有的儿子结点。其目标为尽快找出满足约束条件的一个解。
// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
Queue<Node> q; // 核心数据结构
Set<Node> visited; // 用于记录某个点是否走过,避免走回头路,在二维矩阵迷宫常用boolean[][]记录某个点x,y是否走过
q.offer(start); // 将起点加入队列
visited.add(start);
int step = 0; // 记录扩散的步数
while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj()) { // 一旦活结点成为了扩展结点,就一次性产生其所有的儿子结点。
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
以上模板来自东哥总结,地址:labuladong
我的解答
import java.util.*;
/**
* @program:
* @ClassName Main
* @description:
* @author: wangzp
* @create: 2023-05-06
* @Version 1.0x
**/
public class Main {
static int N;
static int K;
static Set<String> guaiwuset = new HashSet<>(); // 用于存储地图中的静态障碍
static int row1,col1; // 公主(宝藏)
static int row2,col2; // 王子(冒险家)
static Queue<int[]> queue; //BFS的队列中存储每个点及到达该点的时间,每个点为(x,y,t)
static int[][][] graph;// 三维的链接矩阵矩阵存储动态地图
public static void main(String[] args) {
// 1.处理输入
Scanner scanner = new Scanner(System.in);
N = scanner.nextInt();
K = scanner.nextInt();
graph = new int[3][N][N]; // 0 1 2 共三个时间点的地图
for (int i = 0; i < K; i++) {
int x = scanner.nextInt();
int y = scanner.nextInt();
guaiwuset.add(""+x+"-"+y);
}
row1 = scanner.nextInt();
col1 = scanner.nextInt();
row2 = scanner.nextInt();
col2 = scanner.nextInt();
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
String str = scanner.next();
graph[0][i][j] = Integer.valueOf(Character.toString(str.charAt(0)));
graph[1][i][j] = Integer.valueOf(Character.toString(str.charAt(1)));
graph[2][i][j] = Integer.valueOf(Character.toString(str.charAt(2)));
}
}
// 2.解答开始
queue = new LinkedList<>(); // BFS的队列
boolean[][][] visitedSum = new boolean[N][N][3]; // 记录某个时间点的某个位置是否`走过`
queue.add(new int[]{row2,col2,0}); // 将初始节点放入队列
visitedSum[row2][col2][0] = true; // 将初始节点的0时刻标记为`走过`
int time = 0;// 记录时间
while (!queue.isEmpty()){
int q_size = queue.size();
for (int i = 0; i < q_size; i++) {
int[] cur = queue.poll();
int curx = cur[0];
int cury = cur[1];
int curtime = cur[2];
//System.out.println("x="+curx+" y="+cury+" curtime="+curtime);
if(curx == row1 && cury == col1){ // 找到最终目标,直接返回答案
System.out.println(curtime);
return;
}
int[][] fangxiang = new int[][]{{0,-1},{0,1},{-1,0},{1,0},{0,0}}; // 上下左右及原地不动5种选择
for(int[] f:fangxiang){
int newx = curx + f[0];
int newy = cury + f[1];
int newtime = curtime+1;
// 判断越界
if(newx<0||newx>=N||newy>=N||newy<0)continue;
// 判断静态障碍
if(guaiwuset.contains(""+newx+"-"+newy)){ // 有怪兽(陷阱)
continue;
}
// 判断动态障碍
if(graph[newtime%3][newx][newy]==1){ // 下个节点有障碍
continue;
}
// 判断是否走过
if(visitedSum[newx][newy][newtime%3] == true){
continue;
}
queue.offer(new int[]{newx,newy,newtime});
visitedSum[newx][newy][newtime%3] = true;
}
}
}
System.out.println("-1");
}
}