递归——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。



2. 进一步思考:

如果函数的接口中并没有参数 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()和path.remove()的两行代码,分别完成了记录与回溯两步操作:

path.add(direction[i]),用来记录当前步骤;

path.remove(path.size()-1),当子递归函数失败的时候,path需要删除掉最后一个步骤记录,回溯到之前未添加direction[i]的状态。


1 0
原创粉丝点击