这里使用了回溯算法,回溯算法是一种比较特别的DFS,它需要在达到搜索条件后,回溯上一次,继续搜索。普通的DFS适合找路径是否存在的问题,而回溯算法适合解决有几条路径的问题。
这里给出DFS的模板
dfs的模板
```cpp
dfs(){
// 第一步,检查下标是否满足条件
// 第二步:检查是否被访问过,或者是否满足当前匹配条件
// 第三步:检查是否满足返回结果条件
// 第四步:都没有返回,说明应该进行下一步递归
// 标记
dfs(下一次)
// 取消标记,回溯
}
main() {
for (对所有可能情况) {
dfs()
}
}
先来我自己的解法,设置一个和矩阵同等大小的访问数组,默认为false,表示没有被访问过。然后遍历每个位置,调用DFS。
算法复杂度:
- 时间复杂度: O ( 4 k M N ) O(4^kMN) O(4kMN)。这里, k k k指的是字符串的长度, M M M和 N N N分别表示的是矩阵的长和宽。
- 空间复杂度: O ( M N ) O(MN) O(MN),由于新建了一个同等大小的visited数组。
// 这里需要遍历每个位置,来一个DFS,才能求解,
// 一个DFS是无法求解的
class Solution {
char[][] board;
String word;
boolean visited[][];
public boolean exist(char[][] board, String word) {
this.board = board;
this.word = word;
visited = new boolean[board.length][];
// 这里初始化后默认是false
for(int i = 0; i < board.length; i++){
visited[i] = new boolean[board[0].length];
}
// for(boolean[] visit : visited)
// System.out.println(Arrays.toString(visit));
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[i].length; j++){
if(exist(i, j, 0))
return true;
}
}
return false;
}
private boolean exist(int row, int col, int len){
if(len == word.length())
return true;
if(row < 0 || row >= board.length || col < 0 || col >= board[0].length || visited[row] [col] == true || board[row][col] != word.charAt(len))
return false;
boolean ret = false;
visited[row][col] = true;
ret = exist(row + 1, col, len + 1) || exist(row - 1, col, len + 1) || exist(row, col - 1, len + 1) || exist(row, col + 1, len + 1);
// 这里这个恢复为未访问是回溯算法的重点
visited[row][col] = false;
return ret;
}
}
这样太浪费内存了,我们可以直接修改矩阵,用矩阵自己的值来表示状态。例如,把访问过的矩阵元素设置为#
,因为矩阵中不会出现#
。所以出现#
代表已经被访问。
- 时间复杂度: O ( 4 k M N ) O(4^kMN) O(4kMN)。这里, k k k指的是字符串的长度, M M M和 N N N分别表示的是矩阵的长和宽。因为需要对每个元素的4个方向进行长度 k k k的匹配
- 空间复杂度: O ( k ) O(k) O(k),递归深度最多为字符串的长度
//
class Solution {
char[][] board;
String word;
// 原本打算把len当作成员变量,记录已经匹配字符串长度,但是管理起来一直出BUG
// int len = 0;
public boolean exist(char[][] board, String word) {
this.board = board;
this.word = word;
// 对矩阵每个元素进行DFS,匹配成功则返回true;
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[i].length; j++){
if(exist(i, j, 0))
return true;
}
}
// 都不成功,返回false
return false;
}
private boolean exist(int row, int col, int len){
// 检查数组越界以及是否访问以及是否匹配
if(row < 0 || row >= board.length || col < 0 || col >= board[0].length || board[row] [col] == '#' || board[row][col] != word.charAt(len))
return false;
// 若匹配长度完毕,则返回true
if(len == word.length() - 1)
return true;
boolean ret = false;
// 用`#`来记录已经访问状态
board[row][col] = '#';
ret = exist(row + 1, col, len + 1) || exist(row - 1, col, len + 1)
|| exist(row, col - 1, len + 1) || exist(row, col + 1, len + 1);
// 回溯,返回默认状态。
board[row][col] = word.charAt(len);
return ret;
}
}
用C++实现上述思想,不过这次用一个数组记录应该走的位置。不好理解,还是别这么做。
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 == '#' || ch != str[pos]) {
return false;
}
// 如果匹配,判断是否匹配到最后一个字符
if (pos + 1 == str_len) {
return true;
}
// 说明当前字符成功匹配,标记一下,下次不能再次进入
mat[i * w + j] = '#';
for (int k = 0; k < 4; ++k) {
if (dfs(i + dir[k], j + dir[k + 1], pos + 1, str)) {
return true;
}
}
// 如果4个方向都无法匹配 str[pos + 1]
// 则回溯, 将'#' 还原成 ch
mat[i * w + j] = ch;
// 说明此次匹配是不成功的
return false;
}
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) {
if (dfs(i, j, 0, str)) {
return true;
}
}
}
return false;
}
};