学霸的迷宫
原创
问题描述
学霸抢走了大家的作业,班长为了帮同学们找回作业,决定去找学霸决斗。但学霸为了不要别人打扰,住在一个城堡里,城堡外面是一个二维的格子迷宫,要进城堡必须得先通过迷宫。因为班长还有妹子要陪,磨刀不误砍柴功,他为了节约时间,从线人那里搞到了迷宫的地图,准备提前计算最短的路线。可是他现在正向妹子解释这件事情,于是就委托你帮他找一条最短的路线。
输入格式
第一行两个整数n, m,为迷宫的长宽。
接下来n行,每行m个数,数之间没有间隔,为0或1中的一个。0表示这个格子可以通过,1表示不可以。假设你现在已经在迷宫坐标(1,1)的地方,即左上角,迷宫的出口在(n,m)。每次移动时只能向上下左右4个方向移动到另外一个可以通过的格子里,每次移动算一步。数据保证(1,1),(n,m)可以通过。
接下来n行,每行m个数,数之间没有间隔,为0或1中的一个。0表示这个格子可以通过,1表示不可以。假设你现在已经在迷宫坐标(1,1)的地方,即左上角,迷宫的出口在(n,m)。每次移动时只能向上下左右4个方向移动到另外一个可以通过的格子里,每次移动算一步。数据保证(1,1),(n,m)可以通过。
输出格式
第一行一个数为需要的最少步数K。
第二行K个字符,每个字符∈{U,D,L,R},分别表示上下左右。如果有多条长度相同的最短路径,选择在此表示方法下字典序最小的一个。
第二行K个字符,每个字符∈{U,D,L,R},分别表示上下左右。如果有多条长度相同的最短路径,选择在此表示方法下字典序最小的一个。
样例输入
Input Sample 1:
3 3
001
100
110
Input Sample 2:
3 3
000
000
000
3 3
001
100
110
Input Sample 2:
3 3
000
000
000
样例输出
Output Sample 1:
4
RDRD
Output Sample 2:
4
DDRR
4
RDRD
Output Sample 2:
4
DDRR
数据规模和约定
有20%的数据满足:1<=n,m<=10
有50%的数据满足:1<=n,m<=50
有100%的数据满足:1<=n,m<=500。
有50%的数据满足:1<=n,m<=50
有100%的数据满足:1<=n,m<=500。
此题用BFS和DFS都能得出结果,但是用DFS会超时,用BFS一旦找到迷宫出口,即可确定最短路径,
而DFS搜索出每一条可达路径,然后需要比较步数才能确定最短路径,官网也明确提示用广搜解答。
下面说说BFS解答。
此题有两大难点:
一、字典序的比较,即使确定了最短路径的步数,像题目所说,也许存在多条同样步数的最短路径,
我们需要进行字典序比较,然后选出一条字典序最小的,实现并不困难,但是用BFS搜索存在一个重大的
隐患——隐藏了某些路径,我们知道BFS是不用回溯的,不能回溯就导致了某些路径不能被实现,下面我
用题目的第二个例子说明一下:
3 3
000
000
000
000
000
000
假如我们的搜索方向按顺时针右、下、左、上,我们的搜索顺序是这样的:
x.j表示第x步可以到达,在x步到达之内,按照顺时针顺序,此点是第j个被访问的。
最终我们可以有两条路径可以到达终点,即RRDD和RDDR(请大家按照进出顺时针进出队列的顺序自己查照),
发现其他路径DDRR和RDRD等会被省略掉,这是因为那些点已经被访问过,不能重复访问,BFS只负责找到最
短路径,省略了某些最短路径后不可能得出每次都得出正确结果;题目要求字典序最小,其实按照DLRU的访
问顺序就能在到达终点时确定字典序最小的最短路径了。
二、路径的存储,路径的存储可以另外开辟一个迷宫大小的二维数组,到达每一个点,存储这个点的上
一个点到这个点的方向(UDRL其中一个),这样当到达终点时,可以逆序输出最短路径,逆序排序后输出即可。
import java.util.*; public class Main { static int n; //行 static int m; //列 static char maze[][]; //迷宫 static int que_x[]; //横坐标 static int que_y[]; //纵坐标 static char udlr[][]; static int book[][]; //标记 static int dir[][]= { {1,0},{0,-1},{0,1},{-1,0} }; //方向数组DLRU static int step[][]; //存储步数 static int result_step; static int flag=0; static int head=0; //头指针 static int tail=0; //尾指针 static char way[]; //存储路径 static char d_way[]; //中转路径 static void judge_dir(int dx,int dy) { //判断上一步到这一步需要向哪个方向走 //右 if(dx-que_x[head]==0 && dy-que_y[head]==1) { udlr[dx][dy]='R'; } //左 if(dx-que_x[head]==0 && dy-que_y[head]==-1) { udlr[dx][dy]='L'; } //上 if(dx-que_x[head]==-1 && dy-que_y[head]==0) { udlr[dx][dy]='U'; } //下 if(dx-que_x[head]==1 && dy-que_y[head]==0) { udlr[dx][dy]='D'; } } static void find_way(int dx,int dy,int count) { //寻找路径 if(dx==0 && dy==0) { //数组逆序存放于dd_way for(int i=0;i<way.length;i++) { way[i]=d_way[ d_way.length-(i+1) ]; } return; } d_way[count]=udlr[dx][dy]; if(d_way[count]=='U') { //上 find_way(dx+1,dy,count+1); } if(d_way[count]=='R') { //右 find_way(dx,dy-1,count+1); } if(d_way[count]=='D') { //下 find_way(dx-1,dy,count+1); } if(d_way[count]=='L') { //左 find_way(dx,dy+1,count+1); } } public static void main(String[] args) { Scanner reader=new Scanner(System.in); n=reader.nextInt(); m=reader.nextInt(); maze=new char[n][m]; //编号从0开始 udlr=new char[n][m]; //记录四个方向 book=new int[n][m]; step=new int[n][m]; que_x=new int[n*m]; que_y=new int[n*m]; //迷宫赋值 for(int i=0;i<n;i++) { String ss=reader.next(); maze[i]=ss.toCharArray(); } for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { book[i][j]=0; step[i][j]=0; } } book[0][0]=1; //起点 //初始位置入队列 que_x[tail]=0; que_y[tail]=0; tail++; while(head<tail) { for(int i=0;i<4;i++) { int dx=que_x[head]+dir[i][0]; int dy=que_y[head]+dir[i][1]; if(dx<0 || dx>n-1 || dy<0 || dy>m-1) { //越界判断 continue; } if(maze[dx][dy]=='0' && book[dx][dy]==0) { //入队列 que_x[tail]=dx; que_y[tail]=dy; book[dx][dy]=1; tail++; //计算步数 step[dx][dy]=step[ que_x[head] ][ que_y[head] ]+1; //存储方向信息 judge_dir(dx,dy); } //判断终点 if(dx==n-1 && dy==m-1) { //先到步数肯定最少,直接存储结果 result_step=step[dx][dy]; //开辟存放路径空间 way=new char[result_step]; d_way=new char[result_step]; find_way(dx,dy,0); flag=1; } } if(flag==1) { break; } head++; } System.out.println(step[n-1][m-1]); for(int i=0;i<result_step;i++) { System.out.print(way[i]); } } }
DFS(超时)
import java.util.*; public class 学霸的迷宫dfs { static int n; static int m; static char maze[][]; //迷宫 static int book[][]; //标记 static int dir[][]= { {1,0},{0,-1},{0,1},{-1,0} }; //方向 static int min_step=999999999; //最小步数 static char way[][]; //存储方向 static char result_way[]; //存储路径 static char d_way[]; //中转路径 static void find_way(int x,int y,int count) { //倒退寻找路径 //所有路径的首结点为(0,0) if(x==0 && y==0) { return; } d_way[count]=way[x][y]; if(way[x][y]=='R') { //右 find_way(x,y-1,count+1); } if(way[x][y]=='D') { //下 find_way(x-1,y,count+1); } if(way[x][y]=='L') { //左 find_way(x,y+1,count+1); } if(way[x][y]=='U') { //上 find_way(x+1,y,count+1); } } static void dfs(int x,int y,int step) { //step代表步数 //步数超越 if(step>min_step) { return; } //出口判断 if(x==n-1 && y==m-1) { if(step<min_step) { min_step=step; //更新步数 //存储路径 find_way(x,y,0); //结果存储 for(int i=0;i<min_step;i++) { result_way[i]=d_way[ min_step-(i+1) ]; } } } for(int i=0;i<4;i++) { int dx=x+dir[i][0]; int dy=y+dir[i][1]; //越界判断 if(dx<0 || dx>n-1 || dy<0 || dy>m-1) { continue; } //障碍点和访问判断 if(maze[dx][dy]=='1' || book[dx][dy]==1) { continue; } //(x,y)到(dx,dy)的方向判断 if(i==0) { way[dx][dy]='D'; } if(i==1) { way[dx][dy]='L'; } if(i==2) { way[dx][dy]='R'; } if(i==3) { way[dx][dy]='U'; } book[dx][dy]=1; dfs(dx,dy,step+1); book[dx][dy]=0; //回溯 } } public static void main(String[] args) { Scanner reader=new Scanner(System.in); n=reader.nextInt(); m=reader.nextInt(); maze=new char[n][m]; book=new int[n][m]; way=new char[n][m]; result_way=new char[n*m]; d_way=new char[n*m]; for(int i=0;i<n;i++) { String ss=reader.next(); maze[i]=ss.toCharArray(); } for(int i=0;i<n;i++) { for(int j=0;j<m;j++) { book[i][j]=0; } } book[0][0]=1; dfs(0,0,0); //起点(0,0) System.out.println(min_step); for(int i=0;i<min_step;i++) { System.out.print(result_way[i]); } } }
14:01:10
2018-08-02