一、题目
二、题目分析
题意:
这道题的核心是在一个迷宫中,玩家(小蓝)需要从起点走到出口,可以通过破坏最多一堵墙来实现。题目给出了迷宫的大小、起点、出口以及迷宫的布局,墙和可通行的道路分别用不同的符号表示。题目的目标是判断小蓝是否能够通过走路和破坏一面墙到达出口。
迷宫概况: 迷宫是一个 n×m
的矩阵,#
表示墙,.
表示可走的路。小蓝可以从起点通过路径走到出口,并且最多能破坏一面墙。
要求: 如果小蓝能到达出口,输出 “Yes”;如果不能,输出 “No”。
输入说明: 给出迷宫大小m和n
,起点和终点的坐标(A,B)和(C,D)
,接着是迷宫的布局(二维数组
)。
关键点:小蓝最多只能破坏一面墙,因此要考虑两种情况:不破坏墙和破坏一面墙是否能到达终点。
三、解题思路
我们可以考虑使用DFS或者BFS,因为递归比较好写,所以我们优先选择DFS,也就是深度搜索。
不过由于存在障碍物(墙
)的缘故,从起点深搜不一定可以到达终点。对此我们引入染色法来解决这个问题。
第一步:
从起起点坐标(A,B)
开始深度搜索,被搜索过的地方标记为1
,初始时,所有地方标记成0
。倘若通过一次深度搜索,终点(C,D)
被设置成1,那么直接返回Yes。(这是不需要破坏墙的情况):
图中,黑色是墙,蓝色粗线是正确可达终点的路径,周围的细线是递归的分支,表示不可达终点的路径被搜索过。
第二步:
注意:只有在第一次搜索没有到达终点的情况下才会执行接下来的所有步骤。
也就是这种情况:
由于终点黑色墙的阻碍,起点未能搜索到终点。
为了判断是否可以通过破坏一堵墙,到达终点,我们的策略是:
- 从终点在次进行第二次深度搜索,把第一次未搜索的区域进行染色(设置成2),其他地方例如
第一次已经搜索的地方
和墙的位置
我们不做任何处理。 - 遍历每一堵墙,判断墙的周围是否既有第一次搜索过的痕迹,又有第二次搜索过的痕迹,那么说明把这堵墙破坏了,起点和终点就是可达的!
三、代码实现
1、初始化输入输出
import java.util.*;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
//分别表示地图的行、列 起点坐标(A,B) 终点坐标(C,D)
static int m,n,A,B,C,D;
//用来存储地图信息, '.'表示可以通行 '#'表示有墙,不可通行
static char[][] graph;
//用于染色 大小和graph一致
static int[][] color;
//下面这两个数组,用来移动坐标,移动一格 方向分别是“上左下右”
static int[] gX={0,-1,0,1};
static int[] gY={1,0,-1,0};
public static void main(String[] args){
Scanner in=new Scanner(System.in);
//读取数据
m=in.nextInt();
n=in.nextInt();
//初始化color(默认是0)
color =new int[m][n];
//-1的原因是题目给定坐上角的坐标是(1,1) 但是在计算机中,下标是从0开始的
A=in.nextInt()-1;
B=in.nextInt()-1;
C=in.nextInt()-1;
D=in.nextInt()-1;
//读取地图
graph=new char[m][n];
for(int i=0;i<m;i++){
//按行读取字符串
graph[i]=in.next().toCharArray();
}
}
}
gX和gY配套使用,是用来辅助移动的数组,例如当前位置是(X,Y),如果向上移动,我们只需要(X+gX[0],Y+gY[0])即可。
通过四次遍历gX和gY数组,我们就可以判断周围四个方向的路径。
二、设计dfs函数
//flag是1 表示第一次搜索 flag是2表示第二次搜索
private static void dfs(int x,int y,int flag){
//先对当前位置进行标记(染色)
color[x][y]=flag;
//上下左右,四个方向,每一个方向都进行深度搜索
for(int i=0;i<4;i++){
int a=x+gX[i];
int b=y+gY[i];
//注意当前位置的上下左右不一定合法,越界不行 是墙也不行
if(a<0||b<0||a>=m||b>=n||graph[a][b]=='#'||color[a][b]!=0)continue;
dfs(a,b,flag);
}
}
三、使用dfs函数进行搜索
public static void main(String[] args){
Scanner in=new Scanner(System.in);
//读取数据
m=in.nextInt();
n=in.nextInt();
//初始化好color(默认为0)
color=new int[m][n];
//-1的原因是题目给定坐上角的坐标是(1,1) 但是在计算机中,下标是从0开始的
A=in.nextInt()-1;
B=in.nextInt()-1;
C=in.nextInt()-1;
D=in.nextInt()-1;
//读取地图
graph=new char[m][n];
for(int i=0;i<m;i++){
//按行读取字符串
graph[i]=in.next().toCharArray();
}
//第一次搜索,标记成1
dfs(A,B,1);
//如果不需要破坏墙,即可到达终点,那么直接Yes然后返回
if(color[C][D]==1){
System.out.print("Yes");
return;
}
//第二次搜索,从终点开始,到达位置标记成2
dfs(C,D,2);
}
//flag是1 表示第一次搜索 flag是2表示第二次搜索
private static void dfs(int x,int y,int flag){
//先对当前位置进行标记(染色)
color[x][y]=flag;
//上下左右,四个方向,每一个方向都进行深度搜索
for(int i=0;i<4;i++){
int a=x+gX[i];
int b=y+gY[i];
//注意当前位置的上下左右不一定合法,越界不行 是墙也不行
if(a<0||b<0||a>=m||b>=n||graph[a][b]=='#'||color[a][b]!=0)continue;
dfs(a,b,flag);
}
}
四、遍历每一堵墙,判断打破坏后是否可达
由于那个坐标有墙,我们不确定,所以我们可以直接把graph数组遍历一遍,找到是墙的位置。
根据解题思路中所说我们需要找到这样一堵墙:墙的周围(上下左右),既有第一次深搜的痕迹(该位置被标记成1
),也有第二次深搜的痕迹(该位置被标记成2
) 那么打破这堵墙就可以连通起点和终点。
那么代码该如何编写呢?
实际上遍历墙周围的位置还是比较容易的,类似于dfs函数中对gX和gY数组进行for循环即可。
主要是判断墙的周围是否同时有两次搜索过的痕迹,这个少为有点难想。
这里我给大家出一个简单的办法——或运算 '|'
:
- 或运算 只要有1,则为1,例如二进制1001与0110 进行或运算,结果就是1111
- 任意一个数a a|0=a
- 0001(十进制为1)与0010(十进制为2)进行或运算 结果是 0011(十进制=3)
根据或运算的这几个特性,我们直接对color中当前位置的周围数据进行|=运算,结果是3的话,那么这个墙打破,终点和起点就是可以连通的。
//遍历每一堵墙,判断可达性
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(graph[i][j]=='#'){
if(check(i,j)){
System.out.print("Yes");
return;
}
}
}
}
//main函数结束,仍然没有找到,打印No即可
System.out.print("No");
AC代码(Java版)
import java.util.*;
// 1:无需package
// 2: 类名必须Main, 不可修改
public class Main {
//分别表示地图的行、列 起点坐标(A,B) 终点坐标(C,D)
static int m,n,A,B,C,D;
//用来存储地图信息, '.'表示可以通行 '#'表示有墙,不可通行
static char[][] graph;
//用于染色 大小和graph一致
static int[][] color;
//下面这两个数组,用来移动坐标,移动一格 方向分别是“上左下右”
static int[] gX={0,-1,0,1};
static int[] gY={1,0,-1,0};
public static void main(String[] args){
Scanner in=new Scanner(System.in);
//读取数据
m=in.nextInt();
n=in.nextInt();
//初始化好color(默认为0)
color=new int[m][n];
//-1的原因是题目给定坐上角的坐标是(1,1) 但是在计算机中,下标是从0开始的
A=in.nextInt()-1;
B=in.nextInt()-1;
C=in.nextInt()-1;
D=in.nextInt()-1;
//读取地图
graph=new char[m][n];
for(int i=0;i<m;i++){
//按行读取字符串
graph[i]=in.next().toCharArray();
}
//第一次搜索,标记成1
dfs(A,B,1);
//如果不需要破坏墙,即可到达终点,那么直接Yes然后返回
if(color[C][D]==1){
System.out.print("Yes");
return;
}
//第二次搜索,从终点开始,到达位置标记成2
dfs(C,D,2);
//遍历每一堵墙,判断可达性
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(graph[i][j]=='#'){
if(check(i,j)){
System.out.print("Yes");
return;
}
}
}
}
System.out.print("No");
}
//flag是1 表示第一次搜索 flag是2表示第二次搜索
private static void dfs(int x,int y,int flag){
//先对当前位置进行标记(染色)
color[x][y]=flag;
//上下左右,四个方向,每一个方向都进行深度搜索
for(int i=0;i<4;i++){
int a=x+gX[i];
int b=y+gY[i];
//注意当前位置的上下左右不一定合法,越界不行 是墙也不行
if(a<0||b<0||a>=m||b>=n||graph[a][b]=='#'||color[a][b]!=0)continue;
dfs(a,b,flag);
}
}
private static boolean check(int x,int y){
//用于或运算
int u=0;
for(int i=0;i<4;i++){
int a=x+gX[i];
int b=y+gY[i];
//注意,周围如果越界、是墙都必须跳过
if(a>=m||b>=n||a<0||b<0||graph[a][b]=='#')continue;
//进行或运算
u|=color[a][b];
}
return u==3;
}
}