题目描述
题目链接: 迷宫问题_牛客题霸_牛客网 (nowcoder.com)
定义一个二维数组 N*M ,如 5 × 5 数组下所示:
int maze[5][5] = {
0, 1, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 0, 0,
0, 1, 1, 1, 0,
0, 0, 0, 1, 0,
};它表示一个迷宫,其中的1表示墙壁,0表示可以走的路,只能横着走或竖着走,不能斜着走,要求编程序找出从左上角到右下角的路线。入口点为[0,0],既第一格是可以走的路。
数据范围: 2 <= n,m <= 10 , 输入的内容只包含 0 <= val <= 1
输入描述:
输入两个整数,分别表示二维数组的行数,列数。再输入相应的数组,其中的1表示墙壁,0表示可以走的路。数据保证有唯一解,不考虑有多解的情况,即迷宫只有一条通道。
输出描述:
左上角到右下角的最短路径,格式如样例所示。
示例1
输入:
5 5 0 1 0 0 0 0 1 1 1 0 0 0 0 0 0 0 1 1 1 0 0 0 0 1 0复制输出:
(0,0) (1,0) (2,0) (2,1) (2,2) (2,3) (2,4) (3,4) (4,4)
示例2
输入:
5 5 0 1 0 0 0 0 1 0 1 0 0 0 0 0 1 0 1 1 1 0 0 0 0 0 0输出:
(0,0) (1,0) (2,0) (3,0) (4,0) (4,1) (4,2) (4,3) (4,4)说明:
注意:不能斜着走!!
题目解读:
给一个二维数组[ m ][ n ] ,里面只有数值 0 和 1,0 表示通路,1 表示 墙,入口在 (0,0),出口在 (m-1,n-1)处,要求打印出唯一通路的路径 ,最后在打印的时候,要单独添加 括号。题目还是挺好懂的,但其实并不好做。
解题思想:
这道题,我们能想到用 深度优先遍历解题。 大致思路是 给递归函数传入一个 顺序表,让它去收录遍历过程中的路径(为了 记录路径,要设一个坐标类,把每一个坐标当作一个对象存储在顺序表中)。
首先向下遍历,如果没有超出边界,并且不是墙,就继续往下遍历,当以上两个条件任意一个不满足,就在当前位置(不是起始位置),向右遍历,如果右遍历发现不满足条件,那就左遍历,左遍历发现也不满足条件,那就上遍历,上遍历也不满足条件,行,那就把当前坐标从顺序表里删掉,然后回到上一个位置,继续向下,向右,向左,向上遍历。
但这个思路,大家也能发现问题,那就是 遍历过的位置会被重复遍历,那怎么解决这个问题呢? 我们可以把遍历到的每个当前位置,赋值为 1,也就是 通路化墙,这样它在回到上一个位置,发现是墙,就不会把这个位置坐标再放入顺序表了,而且这样也不影响,它在上一个位置 继续向右,向左,向上遍历(向下遍历已经遍历过了),最后返回顺序表打印,就结束了。
代码注释:
import java.util.*;
// 设一个 坐标类
class Position{
int x = 0;
int y =0;
public Position(int x,int y){
this.x = x;
this.y =y;
}
}
public class Main{
// 这个函数用于进行深度遍历,并存储路途的坐标
public static boolean findShortPath(List<Position> path, int row,int column,int[][] map,int x,int y){
// 每次进入函数,先把坐标存储到顺序表中
path.add(new Position(x,y));
// 这是递归终止条件,如果到达[m-1 ][ n-1 ] 位置了,就可以返回了
if(x == row-1 && y == column -1)
return true;
// 把当前遍历到的每个位置,赋值为1, 避免重复遍历
map[x][y] = 1;
// 这是向下遍历
if(x+1 < row && map[x+1][y] == 0){
if(findShortPath(path,row,column,map,x+1, y))
return true;
}
// 这是向右遍历
if(y+1 < column && map[x][y+1] == 0){
if(findShortPath(path,row,column,map,x, y+1))
return true;
}
// 这是向左遍历
if(y > 0 && map[x][y-1] == 0){
if(findShortPath(path,row,column,map,x, y-1))
return true;
}
// 这是向右遍历
if(x >0 && map[x-1][y] == 0){
if(findShortPath(path,row,column,map,x-1, y))
return true;
}
// 如果当前位置 四个方向,都符合遍历条件,就从顺序表中 删除当前坐标
path.remove(path.size()-1);
return false;
}
public static void main(String[] args){
Scanner scan =new Scanner(System.in);
int row = scan.nextInt();
int column = scan.nextInt();
int[][] map = new int[row][column];
for(int i=0;i< row;i++){
for(int j=0;j< column;j++){
map[i][j] = scan.nextInt();
}
}
List<Position> path = new ArrayList<>();
findShortPath(path,row,column,map,0,0);
for(Position position: path){
// 注意题目要求的输出,你要单独添加 括号
System.out.println("("+position.x+","+position.y+")");
}
}
}
代码讲解:
看完上面的解题思路 和 代码注释,解题思想应该是懂了,但对于 遍历的 条件应该还是有疑问的。
首先,为什么 不是用一个循环,对当前坐标,进行依次递归遍历,而是用了 四个if 语句,分别递归遍历。因为你要明白 用循环进行递归 和 四个if 语句递归的区别,比如 出口在 (4,4),你从 (3,4)递归到了(4,4),你到达了出口,此时依照你的终止条件,它返回了上一层递归,可是上一层递归里面,它没有执行完,它不会就此罢手,所以它返回到(3,4)之后,会继续向右遍历,右遍历不符合条件,就向左遍历,左遍历符合条件,它就继续遍历,继续把新的坐标放入顺序表中,所以你就得不到正确的 路径 。 而如果是四个 if语句递归,那么它从(4,3) 回到(3,4)之后,每个if语句,后面都有个 return ,这样它就执行 return 语句,就不会再向右,向左,向上遍历了,然后一直 return ,直到回到第一层递归,函数执行完毕,主函数拿到 顺序表,然后打印即可。 这里要注意,顺序表是引用,所以传一个引用,递归函数放入里面的坐标,主函数可以直接获取到。
第二个问题,为什么要用 Boolean 作为函数的返回条件, 前面说了,由于这个题的特殊性,我们需要四个if 语句来控制递归。当找到出口,递归返回过程中,它不能继续执行其他代码。但是如果没有找到出口,它返回递归的过程中,依然要继续向右,向左,向上遍历,那如何控制这种情况呢,我们就用 Boolean值控制,如果 没找到出口,当前坐标的四个方向又遍历完了,那就返回一个 false给上一层,这样上一层 if 语句接收到 false后,就不执行它里面的 return true语句了 ,而是执行剩余的 if 语句。