给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
输入:board = [[“o”,“a”,“a”,“n”],[“e”,“t”,“a”,“e”],[“i”,“h”,“k”,“r”],[“i”,“f”,“l”,“v”]], words = [“oath”,“pea”,“eat”,“rain”]
输出:[“eat”,“oath”]
比较好想到的做法就是DFS+回溯。需要注意的是将答案存在一个set中,可以有效地去重。
public class leetcode212 {
//DFS+回溯 但是这种方式的时间复杂度较高
Set<String> set = new HashSet<>();
public List<String> findWords(char[][] board, String[] words) {
boolean[][] visit = new boolean[board.length][board[0].length];
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
for (String word : words) {
func(i, j, 0, board, word, visit);
}
}
}
List<String> ans = new ArrayList<>(set);
return ans;
}
public void func(int i, int j, int index, char[][] board, String word, boolean[][] visit) {
if (index >= word.length()) return;//剪枝
if (i >= 0 && i < board.length && j >= 0 && j < board[0].length && !visit[i][j] && board[i][j] == word.charAt(index)) {//剪枝
visit[i][j] = true;
if (index == word.length() - 1) {
set.add(word);
visit[i][j] = false;
return;
}
func(i + 1, j, index + 1, board, word, visit);
func(i, j + 1, index + 1, board, word, visit);
func(i - 1, j, index + 1, board, word, visit);
func(i, j - 1, index + 1, board, word, visit);
visit[i][j] = false;//回溯
}
}
}
上述的方式复杂度比较高,主要的问题出在:每次都需要对每个word进行比对。如果使用前缀树,就可以实现每次DFS的时候通过比对前缀树,即可比对全部所有的word。
//优化的写法
class solution {
//回溯+字典树(前缀树)
int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
Set<String> set = new HashSet<>();
public List<String> findWords(char[][] board, String[] words) {
//构建前缀树
Trie2 trie2 = new Trie2();
for (String word : words) trie2.insert(word);
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
func(i,j,board,trie2);
}
}
return new ArrayList<>(set);
}
public void func(int i, int j, char[][] board, Trie2 cur) {
if (board[i][j]=='#') return;//剪枝 说明这个字符已经使用过了
if (cur.children[board[i][j] -'a'] == null) return;//说明不包含
char ch = board[i][j];
cur = cur.children[board[i][j] - 'a'];
if (cur.isEnd) {
set.add(cur.str);
//这里千万别return 因为有可能后面还会有其他字符 就比如words中的字符串是"os","oss".构建前缀树之后,要是return了,os可以,oss没办法找到了。
}
board[i][j] = '#';
for (int[] dir : dirs) {
int i1 = i + dir[0], j1 = j + dir[1];
if (i1 >= 0 && i1 < board.length && j1 >= 0 && j1 < board[0].length) {
func(i1, j1, board, cur);
}
}
board[i][j] = ch;//回溯
}
}
class Trie2 {
Trie2[] children;
boolean isEnd;
String str;//用于记录字符串结尾时 该字符是谁
public Trie2() {
children = new Trie2[26];
}
public void insert(String word) {
Trie2 cur = this;
for (int i = 0; i < word.length(); i++) {
if (cur.children[word.charAt(i) - 'a'] == null) {
cur.children[word.charAt(i) - 'a'] = new Trie2();
}
cur = cur.children[word.charAt(i) - 'a'];
}
cur.isEnd = true;
cur.str = word;
}
}