剑指offer之回溯算法
运用回溯法解题的关键要素有以下三点:
(1) 针对给定的问题,定义问题的解空间;
(2) 确定易于搜索的解空间结构;
(3) 以深度优先方式搜索解空间,并且在搜索过程中用剪枝函数避免无效搜索。
回溯法的基本思想:能进则进,不进则退,再换一条路走。(即按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择。)
矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路:注意到题目中的要求行走的路径不能重复进入某格子,因此需要创建一个用来存放该点状态(即是否已被访问过,默认为false,已访问则为true)的矩阵,且矩阵大小与原矩阵相对应。
路径的每一次移动规则:只能向上,向下,向左,向右移动一格。
我们可以将路径的行走过程看成是入栈的过程,当矩阵已定位了字符串的前n个字符后,由第n个字符所对应的矩阵的位置分别向上,向下,向左,向右进行试探查找对应第n+1个字符的位置,如果都没有,路径需要回退到第n-1个字符,将原始的第n个字符的标记flag进行调整,并且重新定位第n个字符。
注意给定函数中传入的矩阵并不是以二维的形式传入,而是将矩阵转换为了一维数组,并给定了行和列信息,以此来确定输入的矩阵格式。 因此代码中的表示矩阵中的某一个元素序号使用index = row*cols + col;
public class Solution {
/*判断字符矩阵是否包含某一个字符序列
*/
public boolean hasPath(char[] matrix, int rows, int cols, char[] str) {
boolean[] flag = new boolean[matrix.length];
for(int row = 0 ;row < rows; row++){
for(int col = 0; col < cols;col++){
if(hasPathChar(matrix,rows,cols,row,col,str,0,flag))
return true;
}
}
return false;
}
/**
* 回溯法递归实现判断
* @param matrix 字符矩阵
* @param rows 矩阵行数
* @param cols 矩阵列数
* @param row 当前行索引
* @param col 当前列索引
* @param str 目标字符序列
* @param k 目标字符序列中当前字符索引
* @param flag 字符矩阵是否被访问过标记
* @return
*/
public static boolean hasPathChar(char[] matrix,int rows,int cols,int row,int col,char[] str,int k,boolean[] flag){
int index = row*cols + col;
//超界判定
if(row < 0 ||col < 0||row >= rows || col >= cols || flag[index]||matrix[index] != str[k]){
return false;
}
//终止循环的条件
if(k == str.length - 1){
return true;
}
flag[index] = true;
k++;
//在当前字符的上下左右进行搜索匹配,递归实现
if(hasPathChar(matrix,rows,cols,row-1,col,str,k,flag)||
hasPathChar(matrix,rows,cols,row+1,col,str,k,flag)||
hasPathChar(matrix,rows,cols,row,col-1,str,k,flag)||
hasPathChar(matrix,rows,cols,row,col+1,str,k,flag)){
return true;
}
// 在当前字符的上、下、左、右的元素没有搜索到下一个目标字符,将访问标记重置为false,并且返回false;
flag[index] = false;
return false;
}
}
机器人的运动范围
题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路:
本题思路与上一题大致相同:
都需要定义一个字符矩阵用来标记该点是否被访问过
迭代函数中都需要在开始处进行越界判定
进入迭代函数表示判断已经进行一次了。
总之利用递归实现,每次只能走上下左右四个点,进行判断点的位置是否越界,点数之和是否大于K,是否已经走过了。
public class Solution {
public int movingCount(int threshold, int rows, int cols){
boolean[][] flag = new boolean[rows][cols];
return hasCore(0,0,rows,cols,threshold,flag);
}
public int hasCore(int row,int col,int rows,int cols,int threshold,boolean[][] flag){
if(row<0 ||col<0 ||row>=rows ||col>=cols || numSum(row)+numSum(col)>threshold ||flag[row][col])
return 0;
flag[row][col] = true;
return hasCore(row-1,col,rows,cols,threshold,flag)+
hasCore(row+1,col,rows,cols,threshold,flag)+
hasCore(row,col-1,rows,cols,threshold,flag)+
hasCore(row,col+1,rows,cols,threshold,flag)+1;
}
public int numSum(int i){
int sum = 0;
while(i>0){
sum += i%10;
i = i/10;
}
return sum;
}
}