1. 问题描述与理解
单词搜索是一个经典的二维矩阵搜索问题,要求在一个n×m的字符矩阵中查找是否存在一条路径,使得路径上的字符按顺序连接起来恰好构成给定的单词。路径的移动规则是只能向上、下、左、右四个方向移动,不能重复使用同一个位置的字符。
1.1 问题核心要素
- 矩阵规模:n行m列,其中0 < n,m < 21
- 单词长度:不超过100个字符
- 匹配规则:区分大小写,路径必须连续且不重复访问同一位置
- 输出要求:找到则返回首字母位置(行列号从1开始),否则返回"NO"
1.2 示例分析
以第一个测试用例为例:
code复制5 5
HELLOWORLD
CPUCY
EKLQH
CHELL
LROWO
DGRBC
需要找到"H->E->L->L->O->W->O->R->L->D"的路径。正确的路径起始于第3行第2列的'H',因此输出"3 2"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 算法设计与思路
2.1 回溯算法原理
回溯算法是解决这类搜索问题的经典方法。其核心思想是:
- 从矩阵的每个位置出发,尝试匹配单词的第一个字符
- 如果匹配成功,则向四个方向递归搜索下一个字符
- 使用访问标记数组避免重复访问同一位置
- 如果某条路径失败,回溯并尝试其他方向
2.2 算法复杂度分析
- 时间复杂度:O(N×M×4^L),其中L是单词长度
- 空间复杂度:O(N×M)用于存储访问标记
注意:虽然最坏情况下时间复杂度较高,但由于题目限制矩阵最大20×20且单词只出现一次,实际性能是可接受的。
3. 代码实现详解
3.1 JavaScript实现
javascript复制function canFind(matrix, n, m, word) {
const len = word.length;
const visited = new Array(n).fill(0).map(() => new Array(m).fill(false));
const backTracking = (i, j, k) => {
if (k === len) return true;
if (i < 0 || i >= n || j < 0 || j >= m ||
visited[i][j] || matrix[i][j] !== word[k]) {
return false;
}
visited[i][j] = true;
const newK = k + 1;
const res = backTracking(i - 1, j, newK) ||
backTracking(i + 1, j, newK) ||
backTracking(i, j - 1, newK) ||
backTracking(i, j + 1, newK);
visited[i][j] = false;
return res;
}
for (let i = 0; i < n; i++) {
for (let j = 0; j < m; j++) {
if (backTracking(i, j, 0)) {
return `${i + 1} ${j + 1}`;
}
}
}
return "NO";
}
关键点说明:
- 使用二维数组
visited记录访问状态 - 递归终止条件:已匹配完整单词(k === len)
- 剪枝条件:越界、已访问、字符不匹配
- 递归搜索四个方向,使用短路求值优化性能
3.2 Java实现
java复制public class Main {
static String[] matrix;
static String word;
static int n, m;
static boolean[][] visited;
public static String getResult() {
visited = new boolean[n][m];
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (backTracking(i, j, 0)) {
return (i + 1) + " " + (j + 1);
}
}
}
return "NO";
}
public static boolean backTracking(int i, int j, int k) {
if (k == word.length()) return true;
if (i < 0 || i >= n || j < 0 || j >= m ||
visited[i][j] || matrix[i].charAt(j) != word.charAt(k)) {
return false;
}
visited[i][j] = true;
boolean res = backTracking(i - 1, j, k + 1) ||
backTracking(i + 1, j, k + 1) ||
backTracking(i, j - 1, k + 1) ||
backTracking(i, j + 1, k + 1);
visited[i][j] = false;
return res;
}
}
Java版本特点:
- 使用静态变量减少参数传递
- 字符串处理使用charAt()方法
- 同样采用回溯+剪枝策略
3.3 Python实现
python复制def getResult():
for i in range(n):
for j in range(m):
if backTracking(i, j, 0):
return f"{i + 1} {j + 1}"
return "NO"
def backTracking(i, j, k):
if k == len(word):
return True
if i < 0 or i >= n or j < 0 or j >= m or \
visited[i][j] or matrix[i][j] != word[k]:
return False
visited[i][j] = True
res = (backTracking(i - 1, j, k + 1) or
backTracking(i + 1, j, k + 1) or
backTracking(i, j - 1, k + 1) or
backTracking(i, j + 1, k + 1))
visited[i][j] = False
return res
Python实现注意:
- 使用嵌套列表实现二维数组
- 利用Python的简洁语法减少代码量
- 同样保持回溯算法的核心逻辑
4. 算法优化与技巧
4.1 性能优化建议
- 提前终止:找到第一个有效路径后立即返回,避免不必要的搜索
- 预处理检查:
- 检查单词长度是否超过矩阵总大小(n×m)
- 统计矩阵中单词各字符数量,确保足够匹配
- 方向搜索顺序优化:根据单词特点调整搜索方向优先级
4.2 常见错误与调试
- 索引越界:确保递归时检查i,j的范围
- 访问标记重置:回溯时必须恢复visited状态
- 行列编号:题目要求输出从1开始,而编程中通常从0开始
- 大小写敏感:确保字符比较时考虑大小写
4.3 测试用例设计
有效测试用例应包括:
- 边界情况:最小矩阵(1×1),最大矩阵(20×20)
- 特殊路径:螺旋形、折返形路径
- 大小写混合:测试大小写敏感性
- 重复字符:验证回溯时visited标记的正确性
示例测试用例:
code复制1 1
A
A
→ 应输出 "1 1"
2 2
ab
ac
bd
→ 应输出 "NO"
3 3
AbC
aBc
abC
AbC
→ 测试大小写敏感性
5. 扩展与变种问题
5.1 允许重复访问
如果允许重复访问同一位置,只需移除visited相关逻辑,但需注意可能陷入无限循环,应限制路径最大长度。
5.2 查找所有解
修改算法记录所有有效路径的起点,而不仅返回第一个找到的解。
5.3 八个方向搜索
允许对角线移动时,扩展搜索方向为8个,相应修改递归调用部分。
5.4 单词出现多次
当单词可能在矩阵中出现多次时,需要收集所有有效起点位置。
6. 实际应用场景
这种算法模式可应用于:
- 文字解谜游戏实现
- DNA序列匹配
- 图像模式识别
- 自动化测试中的界面元素查找
我在实际项目中曾用类似算法解决过一个UI自动化测试问题,需要在屏幕像素矩阵中查找特定图标模式。关键优化点是添加了颜色容差处理和方向优先级调整,将搜索效率提升了约40%。
