题目
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路
matrix表示矩阵字符数组,str表示要搜索的字符串,先在matrix中找到str的第一个值,作为第0层(这里的第几层就是字符串str中的第几个值),然后进入递归函数,按照回溯法的基本结构,逐层访问。对于每一层,matrix的访问顺序是上->右->下->左,编号1、2、3、4,如果找到符合的值,就进入下一层。通过一个set类型的visited保存所有访问过的matrix节点下标,每次访问前检查,访问后insert。
回溯法的思想和代码框架
void backtrack (int t)
{
if (t>n)
output(x); //已到叶子结点,输出结果
else
// f(n,t),g(n,t)表示当前扩展结点处未搜索过的子树的起始编号和终止编号
for (int i=f(n,t);i<=g(n,t);i++) {
x[t]=h(i); // h(i):表示在当前扩展结点处x[t]的第i个可选值
//constraint(t)为true表示在当前扩展结点处x[1:t]的取值满足问题的约束条件
//bound(t)为true表示在当前扩展结点处x[1:t]的取值尚未导致目标函数越界
if (constraint(t)&&bound(t))
backtrack(t+1);
}
}
(原文链接:https://blog.csdn.net/u010089444/article/details/53908003)
原代码
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
bool flag=false;
set<int> visited;//需要存储所有访问过的节点,而不仅是上一个访问的
bool hasPath(char* matrix, int rows, int cols, char* str)
{
int size=strlen(str);
bool result=0;
for(int i=0;i<strlen(matrix);i++){
if(matrix[i]==str[0]){
auto ret=visited.insert(i);//存储访问过的节点下标
result=backtrack(matrix,str,1,size,rows,cols,i+1);
if(result==0) visited.clear();//从这个点出发无解,需要清空set再访问下一个起点,不清空的话会对后面的新路径有影响
}
if(result) return true;
}
return 0;
}
bool backtrack(char* matrix,char* str,int t,int size,int rows,int cols,int x){
//t表示当前的层数,size表示str的长度,x表示当前节点在matrix中的下标值+1.
if(t>=size)
return 1;
for(int i=1;i<=4;i++){//回溯法的基本结构
if(i==1){
if(x-cols>=0&&matrix[x-cols-1]==str[t]&&visited.find(x-cols-1)==visited.end()){
auto ret=visited.insert(x-cols-1);
flag=flag|backtrack(matrix,str,t+1,size,rows,cols,x-cols);
//这里为什么用或?见debug
}
}
else if(i==2) {
if(x%cols!=0&&matrix[x+1-1]==str[t]&&visited.find(x)==visited.end()) {
auto ret=visited.insert(x);
flag=flag|backtrack(matrix,str,t+1,size,rows,cols,x+1);
}
}
else if(i==3){
if((x+cols)<=rows*cols&&matrix[x+cols-1]==str[t]&&visited.find(x+cols-1)==visited.end()){
auto ret=visited.insert(x+cols-1);
flag=flag|backtrack(matrix,str,t+1,size,rows,cols,x+cols);
}
}
else{
if(x%cols!=1&&matrix[x-1-1]==str[t]&&visited.find(x-2)==visited.end()){
auto ret=visited.insert(x-2);
flag=flag|backtrack(matrix,str,t+1,size,rows,cols,x-1);
}
}
}
if(flag==true)
return 1;
else
return 0;
}
};
改进代码
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str)
{
bool flag[10000]={0};
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
//把matrix想象成二维数组,循环遍历,找到第一个str元素对应的值的位置,再递归判断四周的值
if(judge(matrix,i,j,rows,cols,flag,str,0))
return true;
}
}
return false;
}
private:
bool judge(char* matrix,int i,int j,int rows,int cols,bool* flag,char* str,int k){
int index=i*cols+j;
//递归终止条件
//1.下标越界 2.值不相等 3.flag中相应的位为true,即点被访问过
if(i<0||j<0||i>=rows||j>=cols||matrix[index]!=str[k]||flag[index]==true)
return false;
//k已经到达str末尾,并且末尾的满足上述条件,说明完全匹配
if(k==strlen(str)-1)
return true;
flag[index]=true;
//回溯,递归寻找,每次找到了就给k+1,找不到flag置为false且返回false
if(judge(matrix,i-1,j,rows,cols,flag,str,k+1)||
judge(matrix,i+1,j,rows,cols,flag,str,k+1)||
judge(matrix,i,j-1,rows,cols,flag,str,k+1)||
judge(matrix,i,j+1,rows,cols,flag,str,k+1))
return true;
//走到这说明此路不通,还原,再试其他路径
flag[index]=false;
return false;
}
};
改进的点
- matrix用二维数组下标来表示,更方便简洁
- 抽象出三种失败情况作为递归终止条件,而不是在每种情况内部判断
- c++中不能声明变长的数组,因此声明了一个可能足够的空间
Debug的点
- 不能只保存上一个访问的点,因为所有走过的点都不能再访问,因此需要用一个全局的数据结构保存走过的路径
- 有可能str的第一个值在matrix中出现过多次,因此就存在多个可能的路径,当否定了一个之后,需要清空visited才能避免影响后面的路径。
- 递归的下一层返回时不能直接return,因为如果直接return就不给剩下的几种情况机会了,例子: