递归——Maze
来源:互联网 发布:网络营销人才需求数据 编辑:程序博客网 时间:2024/06/05 09:52
1. 题目:
Given a maze and a start point and a target point, return whether the target can be reached.
需要实现的递归函数的接口是:public static boolean solveMaze(char[][] maze, int startX, int startY, int targetX, int targetY, boolean[][] visited);
分析:
依照解决递归问题的思路,第一步是找出 base case:当 startX = targetX && startY = targetY 的时候,已经到达目标,返回true;
第二步是写出 recursion rule:对于当前所处位置 (startX, startY),按照一定的方向(例如,右、上、左、下)依次修改坐标,并传入到递归函数中。
如果碰到了base case,即抵达目标,返回true;
如果超出边界或者不能访问(maze[startX][startY] == 'X')或者已经访问过该位置,返回false;
其中是否访问过该位置,用boolean[][] visited 来标记,只要递归函数访问过该位置,visited[startX][startY] = true;
第三步是确定需要的参数:递归函数的接口已经给出,且满足所需参数。
代码:
public class Maze { public static boolean solveMaze(char[][] maze, int startX, int startY, int targetX, int targetY, boolean[][] visited) { if (startX == targetX && startY == targetY) return true; if (startX < 0 || startX >= maze.length || startY < 0 || startY >= maze[0].length || maze[startX][startY] == 'X' || visited[startX][startY]) return false; visited[startX][startY] = true; int[] dx = {1, 0, -1, 0}; int[] dy = {0, 1, 0, -1}; for (int i = 0; i < dx.length; i++) { int newX = startX + dx[i]; int newY = startY + dy[i]; if (solveMaze(maze, newX, newY, startX, startY, visited)) { return true; } } return false; }}
对于递归函数的出口,分别有两处 return true 以及两次 return false:
(1). 当 startX == targetX && startY == targetY,返回true,符合 base case,证明已经抵达目标;
(2). 当前位置越界或者该位置是'X '(代表不能访问)或者visisted[startX][startY]为true(代表已经访问过),不需要继续判断,直接返回 false;
(3). 在循环中,当调用下一层递归函数solveMaze(maze, newX, newY, startX, startY, visited)返回true时,证明该递归路径最后成功抵达目标,在base case下返回了true,所以该递归路径符合条件,此处应该返回true;
(4). 在循环结束时,证明对于当前位置(startX,startY),右上左下四个方向全部探索过,并没有找到成功的路径,所以凡是经过该点都不可能抵达目标,所以最终返回false。
如果函数的接口中并没有参数 boolean[][] visited,该如何判断该位置是否访问过呢?
只要递归函数访问过某个位置(startX, startY),将maze[startX][startY] = 'X',这样递归函数再次访问 (startX, startY)的时候,就会返回false。
这样的函数接口技巧在LeetCode很多 “需要判断是否访问” 的情况下都有应用到。
代码:
public class Maze2 { public static boolean solveMaze(char[][] maze, int startX, int startY, int targetX, int targetY) { if (startX == targetX && startY == targetY) return true; if (startX < 0 || startX >= maze.length || startY < 0 || startY >= maze[0].length || maze[startX][startY] == 'X') return false; maze[startX][startY] = 'X'; int[] dx = {1, 0, -1, 0}; int[] dy = {0, 1, 0, -1}; for (int i = 0; i < dx.length; i++) { int newX = startX + dx[i]; int newY = startY + dy[i]; if (solveMaze(maze, newX, newY, startX, startY) { return true; } } return false; }}
3. 再进一步思考,要求“打印”从出发地到目的地的方法(右、上、左、下)
递归函数的接口变为 public static boolean solveMaze(char[][] maze, int startX, int startY, int targetX, int targetY, String path)
需要一个参数 String path 记录 ”从出发地到目的地的方法“,当最后抵达目标时,即在 base case 的情况下打出 path。
代码:
public class Maze3 { public static boolean solveMaze(char[][] maze, int startX, int startY, int targetX, int targetY, String path) { if (startX == targetX && startY == targetY) { System.out.println(path); return true; } if (startX < 0 || startX >= maze.length || startY < 0 || startY >= maze[0].length || maze[startX][startY] == 'X') return false; maze[startX][startY] = 'X'; int[] dx = {1, 0, -1, 0}; int[] dy = {0, 1, 0, -1}; char[] direction = {'R', 'U', 'L', 'D'}; for (int i = 0; i < dx.length; i++) { int newX = startX + dx[i]; int newY = startY + dy[i]; String newPath = path + direction[i] + " "; if (solveMaze(maze, newX, newY, startX, startY, newPath)) { return true; } } return false; }}
4. 再进一步思考:
通常情况下,比如LeetCode的题目中,不会让你打印出信息,通常是记录信息并返回给调用函数。
这种情况下,我们通常需要使用到一种编程技巧,backtracking,中文翻译为“回溯”。它的意思是在尝试某一种方法时,在本题中就是尝试某一种方向时,如果递归回来的结果不符合预期,需要将记录往回退一步(亦将错误的记录删除)然后才能进行新的操作。
对于递归函数接口的设计,需要一个参数用作记录,该参数的特点是可以回退上一步操作或者删除其中的一些记录,非常符合Java的ArrayList的特性,所以这个参数最好使用 ArrayList<T> 这个数据结构。
代码:
import java.util.*;public class Maze4 { public static boolean solveMaze(char[][] maze, int startX, int startY, int targetX, int targetY, ArrayList<Character> path) { if (startX == targetX && startY == targetY) return true; if (startX < 0 || startX >= maze.length || startY < 0 || startY >= maze[0].length || maze[startX][startY] == 'X') return false; maze[startX][startY] = 'X'; int[] dx = {1, 0, -1, 0}; int[] dy = {0, 1, 0, -1}; char[] direction = {'R', 'U', 'L', 'D'}; for (int i = 0; i < dx.length; i++) { int newX = startX + dx[i]; int newY = startY + dy[i]; path.add(direction[i]); if (solveMaze(maze, newX, newY, startX, startY, path)) { return true; } path.remove(path.size()-1); } return false; }}
path.add(direction[i]),用来记录当前步骤;
path.remove(path.size()-1),当子递归函数失败的时候,path需要删除掉最后一个步骤记录,回溯到之前未添加direction[i]的状态。
- 递归——Maze
- 迷宫问题—Maze
- DFS——Maze Exploration
- POJ3026——Borg Maze
- POJ3026——Borg Maze
- 递归 递归 递归 —深入浅出
- Maze
- maze
- Maze
- maze
- Maze
- maze
- Maze
- Maze
- Maze
- Borg Maze——BFS+最小生成树
- POJ 3026 —— Borg Maze BFS + 最小生成树
- 费用流——危险的迷宫maze
- WCF、WebAPI、WCFREST、WebService之间的区别
- Android 开发中,那些让你相见恨晚的方法、类或接口
- Linux redhat6.5开启ftp服务
- Redis主从设置配置
- 设置Button的字体颜色状态选择器
- 递归——Maze
- Apache与Tomcat联系及区别
- Java String[] 字符串数组去重,排序,toString
- 大型互联网架构之分布式缓存解决方案-Memcached
- iOS开发-GCD 常见用法一(延迟执行)
- 数学建模之预测模型总结
- 蓝桥杯—— 算法训练 数对
- MD5加密的实现
- lightoj 1043 - Triangle Partitioning 公式