79. 单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCCED”
输出:true
示例 2:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “SEE”
输出:true
示例 3:
输入:board = [[“A”,“B”,“C”,“E”],[“S”,“F”,“C”,“S”],[“A”,“D”,“E”,“E”]], word = “ABCB”
输出:false
提示:
- m == board.length
- n = board[i].length
- 1 <= m, n <= 6
- 1 <= word.length <= 15
- board 和 word 仅由大小写英文字母组成
**进阶:**你可以使用搜索剪枝的技术来优化解决方案,使其在 board 更大的情况下可以更快解决问题?
思路:(回溯暴力搜索)
整体思路
使用深度优先搜索(DFS) 和回溯的思想实现。在访问过程中要设置一个二维数组标记元素 hasVisited 是否被访问过。
外层遍历
遍历 board 的所有元素, 并定义函数,从每个结点内层遍历,传入以下参数;
- 变量 k 记录已经匹配的长度
- 该节点的行 r 列 c 坐标
- 数组的遍历情况 hasVisited
- 原字符数组 board
- 需要搜索的字符串 word
内层遍历:递归 + 回溯
- 定义递归返回条件
- 边界情况及不符情况判断
- 如果符合,把该节点标记为已访问
- 再从该节点出发,从四个方向依次递归调用,并判断
- 递归结束后,回溯,即将该节点置位未访问
代码:(Java)
public class words_search {
public static void main(String[] args) {
// TODO 自动生成的方法存根
char[][] board = {
{'A', 'B', 'C', 'E'},
{'S', 'F', 'C', 'S'},
{'A', 'D', 'E', 'E'}
};
String word = "ABCB";
System.out.println(exist(board, word));
}
private static int [][] dircetions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};//可有走的四个方向
private static int m;
private static int n;
public static boolean exist(char[][] board, String word) {
if(word == null || word.length() == 0) {
return true;
}
if(board == null || board.length == 0 || board[0].length == 0) {
return false;
}
m = board.length;//行
n = board[0].length;//列
boolean [][] hasVisited = new boolean[m][n];//是否被访问
for(int r = 0; r < m; r++) {
for(int c = 0; c < n; c++) {
if(backtracking(0, r, c, hasVisited, board, word)) {//数组中的每一个字母都可以当做首字母
return true;
}
}
}
return false;
}
private static boolean backtracking(int k, int r, int c, boolean[][] hasVisited, char[][] board, String word) {
// TODO 自动生成的方法存根
if(k == word.length()) {//比对完成,返回
return true;
}
if(r < 0 || r >= m || c < 0 || c >= n || board[r][c] != word.charAt(k) || hasVisited[r][c]) {//边界及不符合判断
return false;
}
hasVisited[r][c] = true;//访问
for(int [] d : dircetions) {//四个方向依次遍历
if(backtracking(k + 1, r + d[0], c + d[1], hasVisited, board, word)){
return true;
}
}
hasVisited[r][c] = false;//回溯
return false;
}
}
复杂度分析:
-
时间复杂度:一个非常宽松的上界为 O ( M N ⋅ 3 L ) O(MN⋅3^L) O(MN⋅3L),其中 M,N为网格的长度与宽度,L 为字符串 word 的长度。在每次调用函数 backtracking 时,除了第一次可以进入 4 个分支以外,其余时间我们最多会进入 3 个分支(因为每个位置只能使用一次,所以走过来的分支没法走回去)。由于单词长为 L ,故 backtracking 的时间复杂度为 O ( 3 L ) O(3^L) O(3L),而我们要执行 O(MN)次检查。然而,由于剪枝的存在,我们在遇到不匹配或已访问的字符时会提前退出,终止递归流程。因此,实际的时间复杂度会远远小于 O ( M N ⋅ 3 L ) O(MN⋅3^L) O(MN⋅3L)。
-
空间复杂度:O(MN)。我们额外开辟了 O(MN) 的 hasVisited 数组,同时栈的深度最大为O(min(L,MN)) 。
注:仅供学习参考!
题目来源:力扣