文章目录
JZ65矩阵中的路径
参考这里
知识点:
1、使用空字符(Python: ''
, Java/C++: '\0'
)做标记是为了防止标记字符与矩阵原有字符重复。当存在重复时,此算法会将矩阵原有字符认作标记字符,从而出现错误
2、dfs
3、回溯算法适合用递归实现代码。当我们到达某一个节点时,尝试有可能的选项并在满足条件的前提下递归地抵达下一个节点。
4、DFS模板:
dfs(){
##一二三步都是递归的出口
// 第一步,检查下标是否满足条件------因为这是递归函数,每一次跳进来之前[i,j]坐标都会变,所以首先需要判断下标是否满足条件;
// 第二步:检查是否被访问过,或者是否满足当前匹配条件------------坐标变了,对应的元素自然变了,所以要判断新元素是否被访问过,或是否满足当前匹配条件,若被访问过或不满足匹配条件,则跳出本次递归,反之继续递归;
// 第三步:检查是否满足返回结果条件------------------继续递归前需要有终止条件;代码走到这说明该元素没被访问,且符合条件。
// 第四步:都没有返回,说明应该进行下一步递归--------------若不满足终止条件,则继续递归
// 标记
dfs(下一次)
// 回溯
}
main() {
for (对所(原问题)有可能情况) {
//这里原问题是路径的第一步,这里是列举所有可能的第一步;
dfs()
}
}
题解:
1、以矩阵的的左上角作为路径的起点,开始遍历dfs()【第一个dfs()递推找路径的起点】
;
2、dfs()的参数是什么,返回值是什么
dfs的目的是搜索矩阵位置[i,j]
的字符是否存在于字符串
的位置pos
处,所以参数:bool dfs(int i, int j, int pos, char *str);
返回bool型,用来确定是否存在;
3、检查下标是否满足条件;
4、检查是否被访问过,或者是否满足当前匹配条件;
5、检查是否满足返回结果条件
6、都没有返回,说明应该进行下一步递归【第二个dfs()递推,确定一个位置的上右下左是否存在下一个字符】
6.1标记
6.2dfs()
6.3回溯
代码
class Solution {
public:
char *mat = 0;
int h = 0, w = 0;
int str_len = 0;
int dir[5] = {
-1, 0, 1, 0, -1};
bool dfs(int i, int j, int pos, char *str) {
// 第一步,检查下标是否满足条件=======================================================
// 因为dfs调用前,没有进行边界检查,
// 所以需要第一步进行边界检查,
// 因为后面需要访问mat中元素,不能越界访问
if (i < 0 || i >= h || j < 0 || j >= w) {
return false;
}
char ch = mat[i * w + j];
// 第二步:检查是否被访问过,或者是否满足当前匹配条件====================================
// 判断是否访问过,因为一个格子最多只能访问一次。
// 如果没有访问过,判断是否和字符串str[pos]匹配
if (ch == '\0' || ch != str[pos]) {
return false;
}
// 第三步:检查是否满足返回结果条件=================================================
// 如果匹配,判断是否匹配到最后一个字符
if (pos + 1 == str_len) {
return true;//第二个dfs递归的返回条件
}
// 第四步:都没有返回(即,上面一二三步的条件都没满足,return语句都没执行,没提前结束),说明应该进行下一步递归==================================================
//第4.1步:标记------------------------------------------------------
// 说明当前字符成功匹配,标记一下,下次不能再次进入
mat[i * w + j] = '\0';
//第4.2步:dfs(下一次)--------------------------------------------------
for (int k = 0; k < 4; ++k) {
if (dfs(i + dir[k], j + dir[k + 1], pos + 1, str)) {
//第二个dfs递归,递归某元素的上右下左;这里if语句是找到一条路径才判断为真;s
return true;//第一个dfs递归的返回条件
}
}
//第4.3步:回溯------------------------------------------------------
// 如果4个方向都无法匹配 str[pos + 1]
// 则回溯, 将'\0' 还原成 ch
mat[i * w + j] = ch;
// 说明此次匹配是不成功的
return false; //这里返回的是第二个dfs递归,接着上右下左的顺序循环,若已是最后一个左边的判断,则接着回溯到再上一个字符;
}
bool hasPath(char* matrix, int rows, int cols, char* str)
{
mat = matrix;
h = rows, w = cols;
str_len = strlen(str);
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
//两个for循环:从矩阵左上角的格子开始一个一个的遍历,作为路径的起点
if (dfs(i, j, 0, str)) {
//第一个dfs递归
return true;
}
}
}
return false;
}
};
时间复杂度:O(MN*3^k), 每个位置除当前自己的方向,还有3个方向可以展开。k为str的长度;矩阵中共有 MN 个起点
空间复杂度:O(k), 最大递归栈的深度为k
JZ66机器人的运动范围
题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动
,每一次只能向左,右,上,下四个方向
移动一格,但是不能进入行坐标和列坐标的数位之和
大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
题解
1、模式识别:
本题是一个搜索问题,需要遍历矩阵。搜索问题一般用深度优先搜索(DFS:递归实现)或广度优先搜索(BFS:利用队列实现)-------本题以DFS分析,一般首先DFS
2、DFS以递归实现,所以要确定原问题和子问题:
原问题:
从[i,j]出发能走多少步;
子问题:
[i,j]下一步是[i-1,j],[i+1,j],[i,j-1],[i,j+1],递归搜索这四个坐标为起点能走多少步,这是四个子问题。
最后结果
= 所有子问题的解 + 1 ;加1的原因是包括当前[i,j]这个格子。
3、对于矩阵搜索一定要用一个二维数组
记录当前的格子是否被访问过。
DFS模板:
dfs(){
1、递归出口(包括上面模板的一二三步)------下面要进入子问题的递归,进入递归之前当然要先判别递归终止条件;
2、进入子问题递归
2.1标记
2.2子问题递归========》》原问题的解
2.3回溯
}
main{
1、叛别特殊情况;
2、使用二维数组创建标记矩阵vector<vector<bool>> mark(rows,vector<bool>(cols,0));
3、原问题的递归
}
代码
class Solution {
public:
int check(int n){
int sum =0;
while(n){
sum+=n%10;
n/=10;
}
return sum;
}
int dfs(int x,int y, int rows,int cols,int threshold,vector<vector<bool>> &mark){
//不用引用会超时
int count =0;
//第一步递归出口(包含一二三)
if(x>=0 && x<rows && y>=0 && y<cols && mark[x][y]==false && check(x)+check(y)<=threshold){
mark[x][y]=true;
count=1+dfs(x-1,y,rows,cols,threshold,mark)
+dfs(x+1,y,rows,cols,threshold,mark)
+dfs(x,y-1,rows,cols,threshold,mark)
+dfs(x,y+1,rows,cols,threshold,mark);
}
return count;
}
int movingCount(int threshold, int rows, int cols)
{
if(threshold<0) return 0;
vector<vector<bool>> mark(rows,vector<bool>(cols,0));
int count =0;
count=dfs(0,0,rows,cols,threshold,mark);
return count;
}
};
时间复杂度:O(mn), m,n为矩阵大小,每个元素最多访问过一次
空间复杂度:O(mn),其中 m 为方格的行数,n 为方格的列数。搜索的时候需要一个大小为 O(mn) 的标记结构用来标记每个格子是否被走过。
优化
因为所有解是连通的,所以只需要搜索下右两个方向即可。(由于解是连通的,所以即使一个位置向下右搜索不到应有的解,它也会在回溯上一个位置后,通过下右搜索到)
class Solution {
public:
int check(int n){
int sum =0;
while(n){
sum+=n%10;
n/=10;
}
return sum;
}
int dfs(int x,int y, int rows,int cols,int threshold,vector<vector<bool>> &mark){
//不用引用会超时,使用“传引用参数”的形式可以避免拷贝实参,之所以使用引用,是因为拷贝大的“类类型对象”或“容器对象”比较低效,甚至有的类类型根本不支持拷贝操作;
int count =0;
//第一步递归出口(包含一二三)
if(x>=0 && x<rows && y>=0 && y<cols && mark[x][y]==false && check(x)+check(y)<=threshold){
mark[x][y]=true;
count=1+dfs(x+1,y,rows,cols,threshold,mark)
+dfs(x,y+1,rows,cols,threshold,mark);
}
return count;
}
int movingCount(int threshold, int rows, int cols)
{
if(threshold<0) return 0;
vector<vector<bool>> mark(rows,vector<bool>(cols,0));
int count =0;
count=dfs(0,0,rows,cols,threshold,mark);
return count;
}
};
JZ24 二叉树中和为某一值的路径
JZ27 字符串的排列
知识点:
1、函数使用“传引用参数”:
使用“传引用参数”的形式可以避免拷贝实参,之所以使用引用,是因为拷贝大的“类类型对象”或“容器对象”比较低效,甚至有的类类型根本不支持拷贝操作;