文章目录
问题描述
给定一个 m x n
的二维字符网格 board
和一个字符串 word
,要求判断 word
是否存在于网格中。单词必须通过相邻单元格内的字母按顺序连接形成 ,其中"相邻"单元格指的是水平或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 :
输入:
board = [
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]
word = "ABCCED"
输出:true
解释:存在路径 A → B → C → C → E → D
。
方法思路
核心思想:回溯算法
回溯算法通过递归探索所有可能的路径,并在发现路径不满足条件时及时回溯。具体步骤如下:
- 遍历起始点
以每个单元格为起点,尝试匹配单词的第一个字符。 - 递归匹配后续字符
从当前单元格出发,向四个方向(上、下、左、右)递归搜索下一个字符。 - 终止条件
- 成功条件:已匹配所有字符(递归深度等于单词长度)。
- 失败条件:越界、字符不匹配、单元格已访问。
- 标记已访问单元格
通过临时修改当前单元格值为'#'
标记已访问,递归结束后恢复原值。
解决代码
java
class Solution {
public boolean exist(char[][] board, String word) {
// 遍历所有可能的起点
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (backtrack(board, word, i, j, 0)) {
return true;
}
}
}
return false;
}
private boolean backtrack(char[][] board, String word, int i, int j, int k) {
// 成功匹配所有字符
if (k == word.length()) {
return true;
}
// 越界检查
if (i < 0 || i >= board.length || j < 0 || j >= board[0].length) {
return false;
}
// 字符不匹配或已访问
if (board[i][j] != word.charAt(k)) {
return false;
}
// 临时标记当前单元格为已访问
char temp = board[i][j];
board[i][j] = '#';
// 向四个方向递归搜索
boolean res = backtrack(board, word, i + 1, j, k + 1) // 向下
|| backtrack(board, word, i - 1, j, k + 1) // 向上
|| backtrack(board, word, i, j + 1, k + 1) // 向右
|| backtrack(board, word, i, j - 1, k + 1); // 向左
// 恢复单元格原值(回溯)
board[i][j] = temp;
return res;
}
}
代码解释
1. exist
方法
- 功能:遍历所有可能的起始点,调用回溯函数。
- 关键点 :若任意一个起点能匹配成功,则直接返回
true
。
2. backtrack
方法
- 参数说明 :
i, j
:当前单元格坐标。k
:当前匹配的字符在单词中的位置。
- 终止条件 :
k == word.length()
:已匹配所有字符,返回true
。- 越界或字符不匹配:返回
false
。
- 标记与恢复 :
- 标记 :将当前单元格的值改为
'#'
,表示已访问。 - 恢复:递归结束后恢复原值,确保其他路径可正常使用该单元格。
- 标记 :将当前单元格的值改为
- 递归方向:向四个方向递归搜索下一个字符,利用逻辑或的短路特性优化性能。
关键点解析
1. board[i][j] = '#'
的作用
- 防止重复访问:临时标记当前单元格已被访问,避免同一路径重复使用。
- 空间优化 :无需额外使用
visited[][]
数组,空间复杂度从O(mn)
优化为O(1)
。 - 恢复操作:递归结束后恢复原值,确保其他路径的搜索不受影响。
2. 递归方向的选择
- 四个方向:上、下、左、右,覆盖所有可能的相邻路径。
- 逻辑或短路特性 :一旦某条路径返回
true
,后续递归不再执行,直接返回结果。
3. 时间复杂度与空间复杂度
- 时间复杂度 :
O(mn * 4^L)
,其中L
为单词长度。最坏情况下需要遍历所有可能的路径。 - 空间复杂度 :
O(L)
,递归调用栈的深度由单词长度决定。
总结
- 回溯算法的适用性:适合解决路径搜索问题,通过递归探索所有可能性。
- 空间优化技巧:通过修改原数组标记访问状态,避免额外空间开销。
- 恢复操作的必要性:确保不同路径的独立性,避免状态污染。
通过合理设计终止条件和递归逻辑,该算法能够高效解决单词搜索问题,代码简洁且易于理解。