回溯法一般会结合dfs解决问题。先深搜到返回条件,然后回溯到上一层继续dfs。一般见过的回溯法的题都是矩阵形式给出的。另外这种类型的题很多都涉及元素是否被访问过的问题,所以一般的返回条件会有两个:1.访问位置超过边界 2.元素已经被访问过了。还有一个要注意的是,当dfs全部结束的时候也会返回(当然,毕竟是递归吗),有时候也会利用到这个返回,比如LeetCode 200
剑指offer最后的两道题都是回溯法的,一个是判断字母矩阵中是否存在某个单词,另一个是计数机器人所能到达符合条件的方格数量
LeetCode里200. Number of Islands计数连通岛屿的数量。
求岛屿的数量本质上是求矩阵中数字1 的连通数量。我们遍历矩阵,一旦发现元素是1并且没有被访问过,就开始进入dfs函数向四个方向递归dfs,在dfs函数中我们要将访问过的元素标志为已访问(所以需要一个isVisited数组),当四个方向的dfs全部完成后,返回到最开始调用的位置,这时候连通的1已经全部访问到了,也就是一个岛屿被找到了,在这里就可以加1了。
在这道题里,dfs函数做了两件事,一个是深搜全部连通的1,方法就是递归调用,另一个是将这些1标记isVisited,方法是设置了isVisited数组。
特别要注意的是,是如何进行计数的。是在找到所有连通1后,dfs全部结束后返回到最初的调用位置时进行加一。
class Solution {
//回溯法
//求岛屿的数量本质上是求矩阵中1的连通个数,我们用dfs来深搜连通1的数量
//遍历矩阵每个元素,如果已经被访问过或者元素本身是0,则略过
//如果没有被访问过并且是1,那么开始dfs。dfs的作用是将连通的1全部找到,并且标记为访问过。连通的1全部被访问过了,那么返回并将岛屿数量加1
public int numIslands(char[][] grid) {
if(grid==null || grid.length==0 || grid[0].length==0) return 0;
int count = 0;
boolean[][] isVisited = new boolean[grid.length][grid[0].length];
for(int i=0; igrid.length-1 || j<0||j>grid[0].length-1) return;
if(grid[i][j]=='0' || isVisited[i][j]) return;
isVisited[i][j] = true;
dfs(grid, isVisited, i-1, j);
dfs(grid, isVisited, i+1, j);
dfs(grid, isVisited, i, j-1);
dfs(grid, isVisited, i, j+1);
}
}
这道题是在找从一个确定位置出发的机器人能到达几个符合条件的位置。我们思考比交一下上面LeetCode200那道题,这道题实际上是200这道题所找范围的一个小部分,换成200里的环境的话,就是找连通1的个数,也就是一个岛屿中1的个数。所以虽然也是计数问题,这道题的计数应该是在dfs函数里的,因位每找到一个符合条件的就要加一,并且四个方向符合条件的位置都要加。
还有一点不同的是,在dfs里函数里,我们不要在一开始对条件进行判断返回,因为我们在dfs函数要返回计数值,所以我们直接将判断条件作为是否进行dfs的条件,如果符合就进行dfs,否则在最后直接返回计数值就行了。
public class Solution {
public int movingCount(int threshold, int rows, int cols)
{
boolean[] isVisited = new boolean[rows*cols];
int count = movingCountCore(threshold, rows, cols, 0, 0, isVisited);
return count;
}
public int movingCountCore(int threshold, int rows, int cols, int i, int j, boolean[] isVisited){
int count = 0;
if(check(threshold, rows, cols, i, j, isVisited)){
isVisited[i * cols + j] = true;
count = 1 + movingCountCore(threshold, rows, cols, i-1, j, isVisited)
+ movingCountCore(threshold, rows, cols, i+1, j, isVisited)
+ movingCountCore(threshold, rows, cols, i, j-1, isVisited)
+ movingCountCore(threshold, rows, cols, i, j+1, isVisited);
}
return count;
}
public boolean check(int threshold, int rows, int cols, int i, int j, boolean[] isVisited){
if(i>=0 && i=0 && j0){
sum += num%10;
num = num/10;
}
return sum;
}
}
这道题不是计数了,而是判断能否找到一条符合的路径。那么首先循环找到符合的起点,然后开始深搜。直到访问长度达到满足条件的要求说明找到了满足的路径,就可以返回true了。因为有四个方向可以访问,只要有一个方向满足条件就可以继续进行dfs,当有一个完全访问成功了,就返回就行了。另外题目要求不能重复访问一个位置,所以也要有一个isVisited数组。不同于LeetCode200的是,在四个方向访问完还没有找到路径的话,需要返回一个false,而不是简单返回结束既可以了。
public class Solution {
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
if(matrix.length=rows || curCol>=cols) return false;
if(matrix[index]!=str[k] || isVisited[index]==true) return false;
if(k==str.length-1) return true;
isVisited[index] = true;
if(hasPathCore(curRow-1, curCol, rows, cols, k+1, isVisited, matrix, str)||
hasPathCore(curRow+1, curCol, rows, cols, k+1, isVisited, matrix, str)||
hasPathCore(curRow, curCol-1, rows, cols, k+1, isVisited, matrix, str)||
hasPathCore(curRow, curCol+1, rows, cols, k+1, isVisited, matrix, str)){
return true;
}
isVisited[index] = false;
return false;
}
}