前几天参加字节跳动招聘的笔试,遇到了一个走迷宫的题目(笔试题目,就不挂原图了),当时没有做出来,今天周末,上午总结了一下,来说一说这个迷宫到底怎么走

这篇文章将会分为三个部分,分别是:深度优先算法:获得一条路径

广度优先算法:获得最短路径的长度

广度优先算法:在有传送门的迷宫中寻找最短路径

一、深度优先算法:获得一条路径

在这个题目中,不涉及传送门,地图可以这样表示:

其中,1 的位置表示了墙,即不可使用,0 的位置则为路,因为现在值要求获得一条路径,是不是最佳路径我们不管,所以我们可以使用“一条路走到黑”的思路,深度优先。

(注:这里假设的启点为左上,重点为右下,在启点与重点非这种情形下,算法仍然适用)。

具体的做法是这样的:

首先我们先定义一个Position类来存储一下当前位置,也可以用数组,这里新建类是方便表示和理解:

class Position{

int row;

int cow;

public Position(int row,int cow){

this.cow = cow;

this.row = row;

}

public void show(){

System.out.println(this.row + " " + this.cow);

}

}

这里我还定义了一个show方法,是我在写的时候调试用的,大家可以不管或者直接删除。

然后我们需要设立一个栈,当然也可使用队列来存储这条路径,其次,我们还维护一个访问状态的二维数组,避免在一条圈上反复寻找,造成死循环。

public List solutionDFS(int[][] nums){

int rows = nums.length;

int cows = nums[0].length;

int[][] visited = new int[rows][cows];

Stack stack = new Stack<>();

Position p = new Position(0,0); // 记录一下当前位置,在启点 stack.add(p);

visited[0][0] = 1; // 访问状态设置为 1 ,代表已经访问过了 Position temp;

// 只要找到了终点就退出循环 // 始终没有找到,也会导致栈弹空 while (!stack.isEmpty()&& !(p.row == rows-1 && p.cow == cows-1)){

p = stack.peek(); // 获取上一个访问过的位置 // 按照方向 → ↓ ← ↑的顺序依次进行试探性的走一步 // 如果能走通(在迷宫范围内,不是墙,而且没有访问过,就可以认为是可以走) if (p.cow+1

temp = new Position(p.row,p.cow+1);

stack.add(temp);

visited[temp.row][temp.cow] = 1;

}else if (p.row+1

temp = new Position(p.row+1,p.cow);

stack.add(temp);

visited[temp.row][temp.cow] = 1;

}else if (p.cow-1>-1 && nums[p.row][p.cow-1] == 0 && visited[p.row][p.cow-1] != 1) {

temp = new Position(p.row,p.cow-1);

stack.add(temp);

visited[temp.row][temp.cow] = 1;

}else if (p.row-1 >-1 && nums[p.row-1][p.cow] == 0 && visited[p.row-1][p.cow] != 1){

temp = new Position(p.row-1,p.cow);

stack.add(temp);

visited[temp.row][temp.cow] = 1;

}else {

// 如果没有尝试了四个方向都没有走通,说明上一个点的选取有问题,直接弹出 stack.pop();

}

}

// 最后根据还在栈里的的元素,推导出一挑可用路径 if (stack.isEmpty()) return new LinkedList<>();

Deque deque = new LinkedList<>();

for (Position po:stack) {

deque.addLast(new int[]{po.row,po.cow});

}

return (List)deque;

}

这是针对上面的迷宫的一个输出:

路径比较长,我拆成了左右两个部分进行展示。

二、广度优先算法,获得最短路径

如果想要获得一条最短路径,那么我们可以使用广度优先的思路,“一层一层的剥开我的心”

!啊,回来!

广度优先的思路其实也很容易理解,拿到一个点后,根据这个点的步数,更新这个点周围四个方向上的最小步数,直到全局稳定(也就是没有更小值可以更新了)

public int solutionBFS(int[][] nums){

int rows = nums.length;

int cows = nums[0].length;

int[][] count = new int[rows][cows];

// 首先对计数的数组进行初始化 for (int i = 0;i

Arrays.fill(count[i],Integer.MAX_VALUE);

}

count[0][0] = 0;

// 由于深度优先算法是和遍历的层数有关的,所以我们使用双向链表来操作 // 前面添加,后面取用(还可以使用两个栈来进行交替使用) Deque deque = new LinkedList<>();

Position p = new Position(0,0);

deque.add(p);

int[] r = {0,1,0,-1};

int[] c = {1,0,-1,0};

while (!deque.isEmpty()){

p = deque.pollLast();

for (int i = 0; i<4;i++){

int tempR = p.row+r[i];

int tempC = p.cow+c[i];

if (tempR>-1 && tempR-1 && tempC

// 如果能够进行更新,那就将这个位置再次压入队列中,等待下一次更新 if (count[tempR][tempC] > count[p.row][p.cow]+1){

count[tempR][tempC] = count[p.row][p.cow]+1;

Position temp = new Position(tempR,tempC);

deque.addFirst(temp);

}

}

}

}

return count[rows-1][cows-1];

}

这是示例迷宫的输出:

由于和上面是一个地图,所以数过之后你会发现,确实最少路径是17步。

三、广度优先算法:在有传送门的迷宫中寻找最短路径

这是我们这次主要要说的迷宫,有传送门的迷宫。

一个示例的带有传送门的地图可能是这样的:

其中:

-2 表示启点,-3表示终点,0表示普通路径,-1表示墙,大于0的数字则表示传送门(能够保证传送门成对出现)。

我的思考过程是这样的:

由于传送门之间的穿送是不记录步数的,直觉的思路是:当遇到一个传送门时,直接传送,进而继续进行广度优先搜索(寻找最短路径)。

我认为这个思路可行,但是实现起来可能比较麻烦,因为:一方面,你不知道传送门用的顺序

另一方面,你不知道传送门使用的次数,可能是一次,也可能是0次。而广度优先,对一个点的访问极有可能更多次。

下面来说一下我的思路:

仍然是广度优先没有错,但是是两次,另外在两次之间,对传送门的步数取最小值:

public int solutionTransfer(int[][] nums){

int rows = nums.length;

int cows = nums[0].length;

HashMap> hashMap = new HashMap<>();

int endRow=0,endCow=0,startRow=0,startCow = 0;

// 先获得起始位置、终点位置,以及各个传送门的位置// 将传送门的代号和位置保存到hashmap中 for (int i = 0; i

for (int j = 0;j

if (nums[i][j] == -2){

startRow = i;

startCow = j;

}else if (nums[i][j] == -3){

endRow = i;

endCow = j;

}else {

if (nums[i][j]>0){

if ( !hashMap.containsKey(nums[i][j])){

List list = new LinkedList<>();

hashMap.put(nums[i][j],list);

}

hashMap.get(nums[i][j]).add(new int[]{i,j});

}

}

}

}

// 第一步广度优先算法 int[][] count = new int[rows][cows];

for (int i = 0;i

Arrays.fill(count[i],Integer.MAX_VALUE);

}

count[startRow][startCow] = 0;

Deque deque = new LinkedList<>();

Position p = new Position(startRow,startCow);

deque.add(p);

int[] r = {0,1,0,-1};

int[] c = {1,0,-1,0};

while (!deque.isEmpty()){

p = deque.pollLast();

for (int i = 0; i<4;i++){

int tempR = p.row+r[i];

int tempC = p.cow+c[i];

if (tempR>-1 && tempR-1 && tempC

if (count[tempR][tempC] > count[p.row][p.cow]+1){

count[tempR][tempC] = count[p.row][p.cow]+1;

Position temp = new Position(tempR,tempC);

deque.addFirst(temp);

}

}

}

}

// 通过hash来获得每一对传送门,嗖嗖嗖~ for (int targ : hashMap.keySet()){

List list = hashMap.get(targ);

int[] in = list.get(0);

int[] out = list.get(1);

if (count[in[0]][in[1]] < count[out[0]][out[1]]){

count[out[0]][out[1]] = count[in[0]][in[1]];

}else {

count[in[0]][in[1]] = count[out[0]][out[1]];

}

// 将更改过步数的路径继续压队列 // 这里的代码还可以优化,实际上只需要将原来大的那个点压入队列即可 // 也就是放到 if 和else 里面去,虽然看到了,但是,懒 deque.addFirst(new Position(in[0],in[1]));

deque.addFirst(new Position(out[0],out[1]));

}

// 新一轮的广度优先搜索,更新最小步骤数 while (!deque.isEmpty()){

p = deque.pollLast();

for (int i = 0; i<4;i++){

int tempR = p.row+r[i];

int tempC = p.cow+c[i];

if (tempR>-1 && tempR-1 && tempC

if (count[tempR][tempC] > count[p.row][p.cow]+1){

count[tempR][tempC] = count[p.row][p.cow]+1;

Position temp = new Position(tempR,tempC);

deque.addFirst(temp);

}

}

}

}

// 返回最小步骤数 return count[endRow][endCow];

}

针对给出的示例迷宫(带有传送门),测试结果是这样的:

感谢评论区 @华樱 指出的问题,对上面第三个问题做了一点点改动,改动代码如下:

public int solutionTransferS(int[][] nums){

int rows = nums.length;

int cows = nums[0].length;

HashMap> hashMap = new HashMap<>();

int endRow=0,endCow=0,startRow=0,startCow = 0;

// 先获得起始位置、终点位置,以及各个传送门的位置// 将传送门的代号和位置保存到hashmap中 for (int i = 0; i

for (int j = 0;j

if (nums[i][j] == -2){

startRow = i;

startCow = j;

}else if (nums[i][j] == -3){

endRow = i;

endCow = j;

}else {

if (nums[i][j]>0){

if ( !hashMap.containsKey(nums[i][j])){

List list = new LinkedList<>();

hashMap.put(nums[i][j],list);

}

hashMap.get(nums[i][j]).add(new int[]{i,j});

}

}

}

}

int[][] count = new int[rows][cows];

for (int i = 0;i

Arrays.fill(count[i],Integer.MAX_VALUE);

}

count[startRow][startCow] = 0;

Position p = new Position(startRow,startCow);

Deque deque = new LinkedList<>();

deque.addFirst(p);

List list;

int[] r = {0,1,0,-1};

int[] c = {1,0,-1,0};

while (!deque.isEmpty()){

p = deque.pollLast();

for (int i = 0; i<4;i++){

int tempR = p.row+r[i];

int tempC = p.cow+c[i];

if (tempR>-1 && tempR-1 && tempC

if (nums[p.row][p.cow] >0 && nums[tempR][tempC] == nums[p.row][p.cow]){

continue;

}

if (count[tempR][tempC] > count[p.row][p.cow]+1){

count[tempR][tempC] = count[p.row][p.cow]+1;

Position temp = new Position(tempR,tempC);

deque.addFirst(temp);

}

}

}

if (hashMap.containsKey(nums[p.row][p.cow])){

list = hashMap.get(nums[p.row][p.cow]);

for (int[] t:list) {

if (p.row!=t[0] || p.cow!=t[1]){

if (count[t[0]][t[1]] > count[p.row][p.cow]){

count[t[0]][t[1]] = count[p.row][p.cow];

p.row = t[0];

p.cow = t[1];

deque.addFirst(p);

}

break;

}

}

}

}

return count[endRow][endCow];

}

至于仍然存在的代码冗余问题,确实是还没理解足够透彻,欢迎大家继续挑毛病。

想要获得全部代码,欢迎到我的仓库:PluteW/InterviewCode​github.com

java迷宫算法_Java 算法 走出迷宫!相关推荐

  1. 【NOI】6264:走出迷宫/ 2.5基本算法之搜索

    6264:走出迷宫 查看 提交 统计 提问 总时间限制: 1000ms 内存限制: 65536kB 描述 当你站在一个迷宫里的时候,往往会被错综复杂的道路弄得失去方向感,如果你能得到迷宫地图,事情就会 ...

  2. C++实践 走出迷宫

    c和c++学了快一个学期了,感觉也编不出什么像样的程序,不是解决数学问题就是打印图形,感觉没什么劲.今天找了一个有趣的题目来做做,这是imooc上老师布置的一个案例.先不管算法好坏,来实现一个人走出迷 ...

  3. 【浙大脑机接口实验室探秘】人类与AI控制大鼠走出迷宫,C10背负的混合智能未来(视频)...

    (文/杨静 )我对浙江大学的脑机接口研究长久以来抱有朝圣般的浓厚兴趣,主要是受浙江大学计算机学院潘纲教授的影响.他在一次演讲里展示了脑控大鼠在一名研究人员的脑电波控制下,快速走迷宫的神奇现场,这只大鼠 ...

  4. 搜索4--noi6264:走出迷宫

    搜索4--noi6264:走出迷宫 一.心得 可以去看看别人的代码,吸收精华 二.题目 6264:走出迷宫 查看 提交 统计 提问 总时间限制:  1000ms 内存限制:  65536kB 描述 当 ...

  5. 信息学奥赛一本通(1254:走出迷宫)

    1254:走出迷宫 时间限制: 1000 ms         内存限制: 65536 KB 提交数: 9105     通过数: 4245 [题目描述] 当你站在一个迷宫里的时候,往往会被错综复杂的 ...

  6. 信息学奥赛一本通 1254:走出迷宫 | OpenJudge NOI 2.5 6264:走出迷宫

    [题目链接] ybt 1254:走出迷宫 OpenJudge NOI 2.5 6264:走出迷宫 [题目考点] 1. 广搜 迷宫问题 [解题思路] 广搜,迷宫问题模板题. 当数据量很小时,用深搜的方法 ...

  7. BFS:走出迷宫并输出最小步数

    目录 背景 描述 例子 思路 完整代码 收获总结 背景 描述 给定一个n*m大小的迷宫,其中*代表不可通过的墙壁,而"."代表墙壁,S表示起点,T代表重点.移动过程中,如果当前位置 ...

  8. 6264:走出迷宫(DFS和BFS)

    描述 当你站在一个迷宫里的时候,往往会被错综复杂的道路弄得失去方向感,如果你能得到迷宫地图,事情就会变得非常简单. 假设你已经得到了一个n*m的迷宫的图纸,请你找出从起点到出口的最短路. 输入 第一行 ...

  9. 走出迷宫(信息学奥赛一本通-T1254)

    [题目描述] 当你站在一个迷宫里的时候,往往会被错综复杂的道路弄得失去方向感,如果你能得到迷宫地图,事情就会变得非常简单. 假设你已经得到了一个n*m的迷宫的图纸,请你找出从起点到出口的最短路. [输 ...

最新文章

  1. 个人副业在家可做大学生首选小程序创业项目
  2. 自定义android控件EditText 自定义边框 背景
  3. python进行两个大数相加
  4. Nginx反向代理负载均衡时,验证码不正确
  5. 让计算机开口说话教学反思,小班语言教案及教学反思《我会说普通话》
  6. gitlab贡献率_如何为GitLab做贡献
  7. c++ __declspec
  8. mysql 大事物commit慢造成全库堵塞问题
  9. 精通反激电源变压器及电路设计-自己的笔记
  10. 从零开始学编程——DOS命令
  11. handlersocket mysql,MySQL插件HandlerSocket
  12. 计算机520错误,完美解决win7遇到已停止工作问题
  13. 1.什么是方法 2.方法定义俩变量求和打印 3.改进上面的 (在调用里面改值相加) 4.形参与实参 5.计算长方形周长 定义一个方法并打印出来 6.定义一个方法 计算圆的面积打印出来
  14. TOOD: Task-aligned One-stage Object Detection
  15. 如何使用 IntelliJ IDEA中配置PHP开发环境 及项目搭建
  16. DevC++ 下载和安装
  17. 小米Java笔试_JAVA后端笔试试题(一)
  18. HTML5音乐播放器的收获体会,音乐课的收获和心得【两篇】
  19. Android应用接入招商一网通
  20. 工业互联网新发展:基于 HTML5 WebGL 的高炉炼铁厂可视化系统!

热门文章

  1. 手把手教你撸Redis
  2. 报告!这群阿里工程师在偷偷养猪 1
  3. 数据分享|1960-2020年中国1公里分辨率月降水栅格数据集
  4. 不可不懂的UG编程片体转实体技巧
  5. 爱奇艺数据库选型最佳实干--实操篇
  6. Spark:Streaming 实时计算框架理论
  7. 让make最快速度的编译
  8. PHP三元运算符的简写形式
  9. Java关于word转pdf工具方法的几种解决方案和我遇到一些问题(html中转、jacob、Docx4j)
  10. java去掉String里面的空格、换行符等