65、矩阵中的路径
题目描述
请设计⼀个函数,⽤来判断在⼀个矩阵中是否存在⼀条包含某字符串所有字符的路径。路径可以从矩阵中的任意⼀个格⼦开始,每⼀步可以在矩阵中向左,向右,向上,向下移动⼀个格⼦。如果⼀条路径经过了矩阵中的某⼀个格⼦,则该路径不能再进⼊该格⼦。 例如矩阵:

中包含⼀条字符串 " bcced " 的路径,但是矩阵中不包含 " abcb " 路径,因为字符串的第⼀个字符 b占据了矩阵中的第⼀⾏第⼆个格⼦之后,路径不能再次进⼊该格⼦。
示例1
输⼊:[[a,b,c,e],[s,f,c,s],[a,d,e,e]],"abcced"
返回值:true
思路及解答
DFS回溯
主要的思路是对于每⼀个字符为起点,递归向四周拓展,然后遇到不匹配返回 false ,匹配则接着匹配直到完成,⾥⾯包含了 回溯 的思想。步骤如下:
针对每⼀个字符为起点,初始化⼀个和矩阵⼀样⼤⼩的标识数组,标识该位置是否被访问过,⼀开始默认是false 。
- 如果当前的字符索引已经超过了字符串⻓度,说明前⾯已经完全匹配成功,直接返回 true
- 如果⾏索引和列索引,不在有效的范围内,或者改位置已经标识被访问,直接返回 false
- 否则将当前标识置为已经访问过
- 如果矩阵当前位置的字符和字符串相等,那么就字符串的索引加⼀,递归判断周边的四个,只要⼀个的结果为 true ,就返回 true ,否则将该位置置为没有访问过(相当于回溯,退回上⼀步),返回 false 。矩阵当前位置的字符和字符串不相等,否则同样也是将该位置置为没有访问过(相当于回溯,退回上⼀步),返回 false 。

class Solution {
// 记录当前匹配的字符索引(对应 track 的作用)
int index = 0;
// 标记矩阵中某个位置是否已被访问过
boolean[][] used;
public boolean hasPath(char[][] matrix, String word) {
if (matrix == null || matrix.length == 0 || word == null || word.length() == 0) {
return false;
}
int rows = matrix.length;
int cols = matrix[0].length;
used = new boolean[rows][cols];
// 遍历矩阵中的每一个点作为起点
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 只要有一条路径满足条件,直接返回 true
if (backtrack(matrix, word, i, j)) {
return true;
}
}
}
return false;
}
// 回溯算法核心函数
boolean backtrack(char[][] matrix, String word, int row, int col) {
// base case:如果所有字符都已匹配成功
if (index == word.length()) {
return true;
}
// 剪枝条件:越界、已经访问过、或者当前字符不匹配
if (row < 0 || row >= matrix.length || col < 0 || col >= matrix[0].length
|| used[row][col] || matrix[row][col] != word.charAt(index)) {
return false;
}
// 做选择:标记当前节点已访问,准备匹配下一个字符
used[row][col] = true;
index++;
// 进入下一层回溯树:向上下左右四个方向探索
boolean res = backtrack(matrix, word, row - 1, col)
|| backtrack(matrix, word, row + 1, col)
|| backtrack(matrix, word, row, col - 1)
|| backtrack(matrix, word, row, col + 1);
// 撤销选择:如果四个方向都找不到路,必须回退状态(回溯的核心)
if (!res) {
index--;
used[row][col] = false;
}
return res;
}
}- 时间复杂度:O(3^k × m × n),其中k为单词长度,m、n为矩阵尺寸。每个点有3个方向可选(不能回退)
- 空间复杂度:O(k),递归栈深度和visited数组空间
原地标记优化(最优)
通过修改原矩阵来标记访问状态,节省空间。

public class Solution {
public boolean exist(char[][] board, String word) {
if (board == null || board.length == 0 || word == null || word.length() == 0) {
return false;
}
char[] words = word.toCharArray();
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (dfsOptimized(board, words, i, j, 0)) {
return true;
}
}
}
return false;
}
private boolean dfsOptimized(char[][] board, char[] word, int i, int j, int index) {
// 边界检查和字符匹配检查
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length ||
board[i][j] != word[index]) {
return false;
}
// 找到完整路径
if (index == word.length - 1) {
return true;
}
// 原地标记:将当前字符临时替换为特殊标记(不能出现的字符)
char temp = board[i][j];
board[i][j] = '#'; // 标记为已访问
// 四个方向搜索
boolean res = dfsOptimized(board, word, i + 1, j, index + 1) ||
dfsOptimized(board, word, i - 1, j, index + 1) ||
dfsOptimized(board, word, i, j + 1, index + 1) ||
dfsOptimized(board, word, i, j - 1, index + 1);
// 回溯:恢复原始字符
board[i][j] = temp;
return res;
}
}关键技巧:
- 临时修改:将访问过的
board[i][j]改为'#'(或其他不在字母表中的字符) - 自动避障:后续搜索遇到
'#'会因字符不匹配而自动跳过 - 状态恢复:回溯时恢复原始字符,确保不影响其他路径搜索
算法分析:
- 时间复杂度:O(3^k × m × n),与前述方法相同
- 空间复杂度:O(1),显著优化!仅使用常数空间(递归栈空间不可避免)
