剑指offer的一道经典题目,
描述
请设计一个函数,用来判断在一个n乘m的矩阵中是否存在一条包含某长度为len的字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如
[abce
sfcs
adee]
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
数据范围:0≤n,m≤20,1≤len≤25
思路:
我们并没有办法确定起点在哪里,所以我们将矩阵中的每个点都遍历一遍。
找到起点后像上下左右找到合适的下一个点,往后就可以视为递归的子问题了。
- 终止条件: 到了矩阵的边界或者是下一个字符与这个位置的字符不匹配,或者字符串匹配完成,都需要返回,
- 返回值: 将每条查询路径是否有这样的字符串返回,即true or false。
- 本级任务: 检查这个位置的字符与字符串中这个字符是否匹配,并向与它相邻的四个方向(且不是来的方向)延伸子问题。
1 2 3 4 |
|
访问过的节点要将flag值改为true,避免点的重复访问。如果该线路不符合,需要回溯修改flag值。
1 2 3 4 |
|
具体做法:
- 第一步:优先处理矩阵为空的特殊情况。
- 第二步:设置flag数组记录某一次路径中矩阵中的位置是否被经过,因此一条路径不能回头。
- 第三步:遍历矩阵,对每个位置进行递归。
- 第四步:递归查找的时候,到了矩阵的边界或者是下一个字符与这个位置的字符不匹配,或者节点已经访问过了,或者字符串匹配完成都结束递归。
- 第五步:访问节点,修改flag数组,向其他四个方向延伸,回溯的时候修改flag数组。
import java.util.*;
public class Solution {
private boolean dfs(char[][] matrix, int n, int m, int i, int j, String word, int k, boolean[][] flag){
if(i < 0 || i >= n || j < 0 || j >= m || (matrix[i][j] != word.charAt(k)) || (flag[i][j] == true))
//下标越界、字符不匹配、已经遍历过不能重复
return false;
//k为记录当前第几个字符
if(k == word.length() - 1)
return true;
flag[i][j] = true;
//该结点任意方向可行就可
if(dfs(matrix, n, m, i - 1, j, word, k + 1, flag)
||dfs(matrix, n, m, i + 1, j, word, k + 1, flag)
||dfs(matrix, n, m, i, j - 1, word, k + 1, flag)
||dfs(matrix, n, m, i , j + 1, word, k + 1, flag))
return true;
//没找到经过此格的,此格未被占用
flag[i][j] = false;
return false;
}
public boolean hasPath (char[][] matrix, String word) {
//优先处理特殊情况
if(matrix.length == 0)
return false;
int n = matrix.length;
int m = matrix[0].length;
//初始化flag矩阵记录是否走过
boolean[][] flag = new boolean[n][m];
//遍历矩阵找起点
for(int i = 0; i < n; i++){
for(int j = 0; j < m; j++){
//通过dfs找到路径
if(dfs(matrix, n, m, i, j, word, 0, flag))
return true;
}
}
return false;
}
}
复杂度分析:
- 时间复杂度:O(mn∗3^k),其中m与n为矩阵的边长,k为字符串的长度,遍历一次矩阵,每次条递归最坏遍历深度为k,看起来是四个方向,但是有一个方向是来的方向不重复访问,所以是三叉树型递归。
- 空间复杂度:O(mn),辅助二维数组记录是否走过某节点使用了空间。