第一次接触回溯法的题,老实说,看代码的时候是晕晕乎乎的。


进入正题:
1.考虑到从某个格子走到其他格子,还可能回来,再去其他格子,这一个过程可以使用递归,也可以用栈来记录。先使用递归。
2.由于不能重复进入一个格子,所以应该定义一个布尔值,让这个布尔值和格子联系起来。这里是定义一个数组,长度和给的矩阵长度一样,是一维数组。

//这部分思考转换成代码
 boolean[] visited = new boolean[matrix.length];

3.因为任意一种元素都可能作为起点,矩阵是二维的,所以利用两层for循环遍历所有元素,每次都调用一次方法,该方法返回以某个位置为起点的情况。

//思考过程转换成代码
 for (int j = 0; j < rows; j++) {
            for (int i = 0; i < cols; i++) {//每个节点都有可能是起点
                if(dfs(matrix,rows,cols,str,i,j,0,visited)) return true;
//这里多了个k=0来充当str的索引,k用来表示在str数组中装了几个字符
            }
        }

4.假如本次使用的是递归。
首先,思考节点走的情况。走了几步,失败,需要回溯,再走。那么每次走就是调用递归,每次回溯就是返回语句,而之前提到,每遍历一个节点,先记录布尔值,然后进入递归,然后回溯时(),需要重置为false。

//思考过程转换成代码
visited[i + j * cols] = true;
递归调用的相关语句
visited[i + j * cols] = false;//这句话是在递归调用的内部,只是此次单独拿出来分析了
//菜是原罪,我之前居然理解的记录的数组只能用二维的
//实际上,一个一维数组就可以表示整个二维数组的情况
//一个3行4列的数组  i + j * cols在表示第三行第四列的位置时,j=2 i=3  cols是列数(4)
//其中j是行的索引,i是行的索引,索引第四列导致i=3,第三行导致i=2
//就真的很奇妙,妙不可言。

5.设计判断进入递归的标志,以及递归结束的标志。
当我们标记遍历某个位置,并在visited中进行标记后,就应当考虑进行递归了,由于可以上下左右四个方向移动,因此需要一个'或'操作,也就是说四个操作里面有一个可以递归,那么递归就可以启动。因此在结束本次递归时,应当返回true,所以之前标记的递归写法如下

//递归调用的相关语句
 if(dfs(matrix, rows, cols, str, i, j - 1, k + 1, visited) //左移
              || dfs(matrix, rows, cols, str, i + 1, j, k + 1, visited)//下移
              || dfs(matrix, rows, cols, str, i, j + 1, k + 1, visited)//右移
              || dfs(matrix, rows, cols, str, i - 1, j, k + 1, visited))//上移
            return true;

6.在本次调用递归结束后(成功调用),自然返回true。那么最重要的就是递归结束的情况。
成功的时候,自然应该返回true,此时也不用调用下一次递归,因此这返回true的语句在调用递归之上。同时注意到,怎么样叫成功呢,所以肯定有一个成功的条件作为递归结束的标志。那就是str数组装满了。因此需要设计一个str数组的索引,每次遍历一个元素时,该索引也+1,这里的索引就是方法中的参数k。当k=length-1,就说明到头了。

if(k == str.length - 1) return true;
//这句话是成功后的标志,因此这句话应该在下一次递归调用前出现

7.在刚刚的递归调用时,假如失败了,那什么叫做失败呢,并且失败后不应该调用下一次递归,因此这些判断递归结束的语句应该也在递归调用之前,几个边界条件考虑清楚(上下左右不能越界是前4个,布尔数组不为真->为真就走到了重复的格子,以及元素要相等->要和题目给的字符串相等才行啊)

 if(i < 0 || i >= cols 
   || j < 0 || j >= rows 
   || visited[i + j * cols] 
   || matrix[i + j * cols] != str[k])
            return false;

递归结束的标志有了,当告诉上一次递归调用结果为false后,上一次的递归会重置visited数组,重置完后呢,应该返回false表示这一次搜索失败,回溯选择其他方式。
一共有两种情况return false,一个是搜索到头了,提示换一条路,也是终止条件。一个是失败后,递归在层层返回时的return false。同样的有两种return true,一个是递归成功调用后,在层层返回时的return true,一个是成功递归到最深处,完成了一次题目要求的搜索,然后return ture

visited[i + j * cols] = false;
return false;
//这是层层返回false的情况,每返回一次时,会清除一个visited标记

8.综合起来,大佬的代码就出来了

public class Solution {

public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix == null || matrix.length != rows * cols 
                || str == null || str.length == 0 
                || str.length > matrix.length) 
            return false;
        boolean[] visited = new boolean[matrix.length];
        for (int j = 0; j < rows; j++) {
            for (int i = 0; i < cols; i++) {//每个节点都有可能是起点
                if(dfs(matrix,rows,cols,str,i,j,0,visited)) return true;
            }//这里多了个k=0来充当str的索引
        }
        return false;

    }

private boolean dfs(char[] matrix, int rows, int cols, char[] str, 
                    int i, int j, int k, boolean[] visited) {
        if(i < 0 || i >= cols || j < 0 || j >= rows ||
           visited[i + j * cols] || matrix[i + j * cols] != str[k])
            return false;
        if(k == str.length - 1) return true;//成功
        visited[i + j * cols] = true;
        if(dfs(matrix, rows, cols, str, i, j - 1, k + 1, visited)
              || dfs(matrix, rows, cols, str, i + 1, j, k + 1, visited)
              || dfs(matrix, rows, cols, str, i, j + 1, k + 1, visited)
              || dfs(matrix, rows, cols, str, i - 1, j, k + 1, visited))
            return true;
        visited[i + j * cols] = false;
        return false;
    }
}

思路2 使用栈记录
在看懂递归写法后,这一部分就轻松了很多。每进一次,都把周围合适的点加入到栈,并将对应的标记设为true,并且str+1。退的时候,弹出的元素的标记重置,而str-1。找了一版比较容易看到的非递归写法。

import java.util.Stack;
public class Solution {
    private static int col;
    public static boolean hasPath(char[] matrix, int rows, int cols, char[] str){
        col = cols;
        if(matrix.length==0||str.length==0||str.length>matrix.length)
             return false;
        for (int total = 0; total < matrix.length; total++) {
            if (matrix[total] == str[0]) {   //只有第一个元素相同时,才调用方法压栈
                if (checkpath(matrix, total, str)) return true;
            }
        }
        return false;
    }
    private static boolean checkpath(char[] matrix, int in, char[] str) {
        if(str.length==1) return matrix[in]==str[0];
        Stack<Integer> sta = new Stack<Integer>();
        sta.push(in);
        int k = 1, i, j, index;
        int back[] = new int[matrix.length];//使用一个辅助数组
        back[in] = 1; //in是传入的位置,将首位置标记置1(back是int数组)
        boolean findflag;
        while (!sta.empty()) { //栈为空时,一次搜寻结束,只有首元素相同时,才入栈
//这是通过两层for循环控制的
            index = sta.peek();
            findflag = true;//第一个元素找到了,所以初始的标志true
            j=index%col;   //j是第几列
            i=(index-j)/col; //i是第几行
            if ((index - col >= 0) && //上
                    back[index - col] == 0 &&
                    matrix[index - col] == str[k]) {
                index -= col;
            } else if ((index + 1 < (i + 1) * col) &&//右
                    (back[index + 1]) == 0 &&
                    matrix[index + 1] == str[k]) {
                index += 1;
            } else if ((index + col < matrix.length)//下
                    && (back[index + col] == 0)
                    && matrix[index + col] == str[k]) {
                index += col;
            } else if ((index - 1 >= i * col) &&//左
                    (back[index - 1] == 0) &&
                    matrix[index - 1] == str[k]) {
                index -= 1;
            } else {
                findflag = false;//没有找到
            }
            if (findflag && back[index] == 0) {
//一个标记上一个元素找到没,一个是下一个元素是否重复查到过。
                sta.push(index);
                k++;//字符窜str下标推进
                if (k == str.length) return true;//已经遍历完str
            } else {
                sta.pop();
                k--;
            }
            back[index] = 1;//无论有无都标记
        }
        return false;
    }
}

总结了一些解题时候的思路,希望下次复习的时候,可以自己独立写出来。