
📖📖走迷宫一类的问题一般都是暴力搜索解决,搜索的方法有两种:深度优先(DFS)和广度优先(BFS),而提到DFS就离不开递归,涉及到递归的问题理解起来还是有难度的,代码编写不当很容易造成栈溢出。
🌻🌻今天就用三道走迷宫问题带你彻底搞懂怎么用DFS秒杀迷宫类问题~
三道练习题目全部来源于计蒜客平台。
| 题目 | 链接 |
|---|---|
| 迷宫(一) | https://nanti.jisuanke.com/t/T1595 |
| 迷宫(二) | http://nanti.jisuanke.com/t/T1596 |
| 迷宫(三) | https://nanti.jisuanke.com/t/T1597 |
😎不废话,直接上题,题来~


一天蒜头君掉进了一个迷宫里面,蒜头君想逃出去,可怜的蒜头君连迷宫是否有能逃出去的路都不知道。
看在蒜头君这么可怜的份上,就请聪明的你告诉蒜头君是否有可以逃出去的路。
输入格式
第一行输入两个整数 n 和 m,表示这是一个 n×m 的迷宫。
接下来的输入一个 n 行 m 列的迷宫。其中
'S'表示蒜头君的位置,'*'表示墙,蒜头君无法通过,'.'表示路,蒜头君可以通过'.'移动,'T'表示迷宫的出口(蒜头君每次只能移动到四个与他相邻的位置——上,下,左,右)。输出格式
输出一个字符串,如果蒜头君可以逃出迷宫输出
"yes",否则输出"no"。数据范围
1<=n,m<=10。
输出时每行末尾的多余空格,不影响答案正确性
样例输入1
3 4 S**. ..*. ***T样例输出1
no样例输入2
3 4 S**. .... ***T样例输出2
yes
👩🏻🏫题目让我们判断给定的迷宫是否有可行解,也就是能否从S走到T,但是不要着急,让我们先来处理一下输入:
⭐先设置两个全局变量n,m用来接收迷宫的行数与列数,同时定义一个全局的二维char数组用来存储迷宫。
public static int n;
public static int m;
public static char[][] maze;
⭐在Java中是不能读入单个字符的,我们可以直接读取一行字符串,再转化为数组,不用担心,字符串转数组在Java中已经封装好了。
⭐注意S的位置题目中并没有说是在左上角,所以在输入的时候还要存储下S的位置。
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
maze = new char[n][m];
int x = 0, y = 0;
for (int i = 0; i < n; i++) {
String str = sc.next();
int start = str.indexOf('S');
if (start != -1) {
x = i;
y = start;
}
maze[i] = str.toCharArray();
}
sc.close();
⭐输入处理好了,现在来思考一下怎么用DFS走迷宫。
⭐在走迷宫时肯定不能走到迷宫外面,不妨写一个方法用来判断现在的位置是否还在迷宫内。
public static boolean inMaze(int x, int y) {
return (0 <= x && x < n && 0 <= y && y < m);
}
⭐仅仅判断是否在迷宫内还不够,我们在走迷宫时还要防止来回兜圈子,所以还需要一个二维数组用来标记走过的位置,走过某个位置以后就不再走了。
public static boolean[][] vis;
同时在主方法中对数组进行实例化:vis = new boolean[n][m]
⭐有了以上的准备工作,现在我们可以来正式编写DFS的代码了,首先定义一个方法,用来表示搜索到了某个位置,返回值是Boolean类型,用来返回迷宫是否有可行解(当然也可以返回void,用全局变量来表示迷宫是否可行):
public static boolean dfs(int x, int y)
由于后面要用到递归,这里也很容易想到递归出口,走到T就不走了:
if (maze[x][y] == 'T') {
return true;
}
然后我们可以将当前位置进行标记,并依次尝试上左下右(习惯用逆时针)四个方向是否可以走,递归调用dfs()。
public static boolean dfs(int x, int y) {
// 递归出口
if (maze[x][y] == 'T') {
return true;
}
// 标记已经走过
vis[x][y] = true;
int tx = x - 1, ty = y;
if (inMaze(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') {
// 可以向这个方向走,并且能走出去,返回true
if (dfs(tx, ty))
return true;
}
tx = x;
ty = y - 1;
if (inMaze(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') {
if (dfs(tx, ty))
return true;
}
tx = x + 1;
ty = y;
if (inMaze(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') {
if (dfs(tx, ty))
return true;
}
tx = x;
ty = y + 1;
if (inMaze(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') {
if (dfs(tx, ty))
return true;
}
return false;
}
⭐如果你觉得尝试四个方向的代码太长了,还可以简化一下,使用一个数组来表示要走的方向。
public static int[][] dir = new int[][]{
{-1, 0}, {0, -1}, {1, 0}, {0, 1}
};
使用循环来向四个方向尝试:😉
for (int i = 0; i < 4; i++) {
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (inMaze(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') {
if (dfs(tx, ty)) {
return true;
}
}
}
⭐将上面的代码组织起来,这道题目就解决了。
import java.util.Scanner;
public class Main {
public static int n;
public static int m;
public static char[][] maze;
public static boolean[][] vis;
public static int[][] dir = new int[][]{
{-1, 0}, {0, -1}, {1, 0}, {0, 1}
};
public static boolean inMaze(int x, int y) {
return (0 <= x && x < n && 0 <= y && y < m);
}
public static boolean dfs(int x, int y) {
if (maze[x][y] == 'T') {
return true;
}
vis[x][y] = true;
for (int i = 0; i < 4; i++) {
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (inMaze(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*') {
if (dfs(tx, ty)) {
return true;
}
}
}
return false;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
maze = new char[n][m];
vis = new boolean[n][m];
int x = 0, y = 0;
for (int i = 0; i < n; i++) {
String str = sc.next();
int start = str.indexOf('S');
if (start != -1) {
x = i;
y = start;
}
maze[i] = str.toCharArray();
}
sc.close();
String ans = dfs(x, y) ? "yes" : "no";
System.out.println(ans);
}
}



蒜头君在你的帮助下终于逃出了迷宫,但是蒜头君并没有沉浸于喜悦之中,而是很快的又陷入了思考,从这个迷宫逃出的最少步数是多少呢?
输入格式
第一行输入两个整数 n 和 m,表示这是一个 n×m 的迷宫。
接下来的输入一个 n 行 m 列的迷宫。其中
'S'表示蒜头君的位置,'*'表示墙,蒜头君无法通过,'.'表示路,蒜头君可以通过'.'移动,'T'表示迷宫的出口(蒜头君每次只能移动到四个与他相邻的位置——上,下,左,右)。输出格式
输出整数,表示蒜头君逃出迷宫的最少步数,如果蒜头君无法逃出迷宫输出 -1−1。
数据范围
1<=n,m<=10
输出时每行末尾的多余空格,不影响答案正确性
样例输入1
3 4 S**. ..*. ***T样例输出1
-1样例输入2
3 4 S**. .... ***T样例输出2
5
👩🏻🏫这道题和上一道题的区别在于它让我们得到迷宫可行解的最少步数,因此我们可以在上一到题的基础上,加入全局变量step用来计数,同时加入minStep全局变量保存最少的步数。
本题我们尝试让函数返回void,并用全局变量flag标记迷宫能否走通。
public static int step = 0;
public static int minStep = Integer.MAX_VALUE;
⭐每走一步,step加一,当走到T时就更新我们的最小路径:
if (maze[x][y] == 'T') {
minStep = Math.min(minStep, step);
flag = true;
return;
}
⭐迷宫的可行解不只有一个,上一题中的dfs()在找到一种方案后就直接返回true了,不会尝试其他走法,因此为了搜索所有方案,我们需要用到深度优先搜索中一个很重要的技巧:回溯。
👉🏻回溯简单理解就是在我们每次做出动作后,都要在递归调用的后面撤销刚刚的动作。
比如我们标记了vis[x][y] = true,并让step++,在递归调用后还要将其撤销,这样dfs()函数就会搜索所有可行的方案:
public static void dfs(int x, int y) {
if (maze[x][y] == 'T') {
minStep = Math.min(minStep, step);
flag = true;
return;
}
vis[x][y] = true;
step++;
for (int i =0; i < 4; i++) {
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (in(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*')
dfs(tx, ty);
}
// 回溯,不然搜出一条可行路径就不会继续搜了
vis[x][y] = false;
step--;
}
⭐看到这里的小伙伴是不是以为这道题终于结束了,不要高兴的太早,这里的代码虽然没有问题,但是提交测试会超时,所以我们还需要对dfs()进行剪枝。
👉🏻所谓剪枝就是剪掉一些不可能的方案,减少搜索的次数。
👉🏻本题的剪枝比较简单,当我们的步数已经超过之前某个方案得到最小路径时,接着走不可能得到更小的步数,这样的话就没必要继续往下走了,直接让函数返回。
if (step > minStep)
return;
🍻至此,本题已经搞定了。
import java.util.Scanner;
public class Main {
public static int n;
public static int m;
public static char[][] maze;
public static boolean[][] vis;
public static int[][] dir = new int[][]{
{-1, 0}, {0, -1}, {1, 0}, {0, 1}
};
public static boolean flag = false;
public static int step = 0;
public static int minStep = Integer.MAX_VALUE;
public static boolean in(int x, int y) {
return 0 <= x && x < n && 0 <= y && y < m;
}
public static void dfs(int x, int y) {
// 剪枝,不然会超时。
// 当前步数比之前方案的步数还大,这种情况直接排除,不可能是最佳答案。
if (step > minStep)
return;
if (maze[x][y] == 'T') {
minStep = Math.min(minStep, step);
flag = true;
return;
}
vis[x][y] = true;
step++;
for (int i =0; i < 4; i++) {
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (in(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '*')
dfs(tx, ty);
}
// 回溯,不然搜出一条可行路径就不会继续搜了
vis[x][y] = false;
step--;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
maze = new char[n][m];
vis = new boolean[n][m];
int x = 0, y = 0;
for (int i = 0; i < n; i++) {
String str = sc.next();
int j = str.indexOf('S');
if (j != -1) {
x = i;
y = j;
}
maze[i] = str.toCharArray();
}
sc.close();
dfs(x, y);
minStep = flag ? minStep : -1;
System.out.println(minStep);
}
}



经过思考蒜头君终于解决了怎么计算一个迷宫的最短路问题,于是蒜头君找到一个新的迷宫图,来验证自己是否真的会计算一个迷宫的最短路。
为了检验自己计算的是否正确,蒜头君特邀你一起来计算。
输入格式
第一行输入两个整数 n 和 m,表示这是一个 n×m 的迷宫。
接下来的输入一个 n 行 m 列的迷宫。其中
'@'表示蒜头君的位置,'#'表示墙,蒜头君无法通过,'.'表示路,蒜头君可以通过'.'移动,所有在迷宫最外围的'.'都表示迷宫的出口(蒜头君每次只能移动到四个与他相邻的位置——上,下,左,右)。输出格式
输出整数,表示蒜头君逃出迷宫的最少步数,如果蒜头君无法逃出迷宫输出 -1−1。
数据范围
1<=n,m<=15。
输出时每行末尾的多余空格,不影响答案正确性
样例输入1
9 13 ############# #@..........# #####.#.#.#.# #...........# #.#.#.#.#.#.# #.#.......#.# #.#.#.#.#.#.# #...........# #####.#######样例输出1
11样例输入2
4 6 #.#### #.#.## #...@# ######样例输出2
5
🍬这道题主要是对前面两道题目的总结,给大家练下手,学会了前面两道,这道题应该可以秒杀了~
😎不多解释,直接给出代码~
import java.util.Scanner;
public class Main {
public static int n;
public static int m;
public static boolean flag = false;
public static int step = 0;
public static int minStep = Integer.MAX_VALUE;
public static char[][] maze;
public static boolean[][] vis;
public static int[][] dir = new int[][]{
{-1, 0}, {0, -1}, {1, 0}, {0, 1}
};
public static boolean in(int x, int y) {
return 0 <= x && x < n && 0 <= y && y < m;
}
public static void dfs(int x, int y) {
if (step > minStep)
return;
if (maze[x][y] == '.' && (x == 0 || x == n - 1 || y == 0 || y == m - 1)) {
flag = true;
minStep = Math.min(minStep, step);
return;
}
vis[x][y] = true;
step++;
for (int i = 0; i < 4; i++) {
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (in(tx, ty) && !vis[tx][ty] && maze[tx][ty] != '#') {
dfs(tx, ty);
}
}
vis[x][y] = false;
step--;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
m = sc.nextInt();
maze = new char[n][m];
vis = new boolean[n][m];
int x = 0, y = 0;
for (int i = 0; i < n; i++) {
String str = sc.next();
int j = str.indexOf('@');
if (j != -1) {
x = i;
y = j;
}
maze[i] = str.toCharArray();
}
sc.close();
dfs(x, y);
int ans = flag ? minStep : -1;
System.out.println(ans);
}
}

🍌🍌🍌
看了上面三道题目不知道屏幕前的你对DFS是不是有了更进一步的理解呢?笔者感觉DFS理解起来还是有难度的,希望友友们多加练习哦~
🍍🍍🍍
创作不易,如果觉得本文对你有所帮助的话请动动小手,给博主点个免费的赞吧。🙇🏻♀️
🍉🍉🍉
@作者:Mymel_晗,计科专业大学牲菜狗一枚,请大佬们多多关照~

我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我使用Nokogiri(Rubygem)css搜索寻找某些在我的html里面。看起来Nokogiri的css搜索不喜欢正则表达式。我想切换到Nokogiri的xpath搜索,因为这似乎支持搜索字符串中的正则表达式。如何在xpath搜索中实现下面提到的(伪)css搜索?require'rubygems'require'nokogiri'value=Nokogiri::HTML.parse(ABBlaCD3"HTML_END#my_blockisgivenmy_bl="1"#my_eqcorrespondstothisregexmy_eq="\/[0-9]+\/"#FIXMEThefoll
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.