题目:原题见lintcode链接, 或者复制以下链接到浏览器
https://www.lintcode.com/problem/word-search-ii/description?_from=ladder&&fromId=161
132. Word Search II
Given a matrix of lower alphabets and a dictionary. Find all words in the dictionary that can be found in the matrix. A word can start from any position in the matrix and go left/right/up/down to the adjacent position. One character only be used once in one word. No same word in dictionary
Example
Example 1:
Input:["doaf","agai","dcan"],["dog","dad","dgdg","can","again"]
Output:["again","can","dad","dog"]
Explanation:
d o a f
a g a i
d c a n
search in Matrix,so return ["again","can","dad","dog"].
Example 2:
Input:["a"],["b"]
Output:[]
Explanation:
a
search in Matrix,return [].
Challenge
Using trie to implement your algorithm.
这道题有两种思路
1)遍历词典中的每个单词,check单词是否在矩阵中
2)遍历矩阵中每个单词,check是否在词典中
这两个思路优选选哪个?
一般选2),因为2)的效率比较高,1)的话要多次遍历矩阵
先通过一个例子模拟解法
matrix:
d o a f
a g a i
d c a n
dictionary:
["dog","dad","dgdg","can","again"]
第一步,找到一个起点[0,0]位置上的字符d, 含有d前缀的词出现在dictionary里面,继续搜索下一个字符。 往右, d -> o ,没有do前缀的词,那么退回到上一级。往下, d -> a,存在含有da前缀的词,继续。 往右,d -> a ->g, 不存在,返回上一级。往下, d -> a -> d, dad是dictionary中的一个词,把这个词加到结果中。继续寻找, d -> a -> d -> c, 不存在,一层层返回到上级, 到起始位置d。 以d开头的单词搜索完毕。
接下来从[0,1]位置上的o开始, 重复第一步操作。
把matrix中的每一个字符作为起始点搜索。
整个搜索过程就是我们熟悉的深度优先搜索(depth priority search).
这道问题的另一个难点就是如何匹配matrix中字符组成的前缀与dictionary中的词。有两种方法:
1)hashset
2)trie(字典树)
方法一用hashset构建前缀set,比如上诉例子的前缀set就是{“d”, “do”, “da”, “dg”, “dgd”, “c”, “ca”, “a”, “ag”, “aga”, “agai”, dog",“dad”,“dgdg”,“can”,“again”}
方法二字典树
关于字典树的java实现可以参考下面两个链接
那么我们把思路总结一下
//建立字典树
//遍历矩阵中每个字符
//任意字符作为起始字符
//dfs搜索下一个字符
//如果当前字符不在字典树上,则退出
//如果当前字典树节点是叶子节点,把整条分支上的字符构成的单词加到结果中
//遍历当前位置的上下左右字符
//加上字符继续dfs
首先建立字典树
要建树,先建节点
label是每个节点上的字符
children就是一个用来存放下一级的子节点
isEnd表明是否是一个单词的终止节点。比如dictionary = [“dark”, “darkness”]. d -> a -> r -> k(isEnd=true) -> n -> e -> s ->s(isEnd=true)
class TrieNode {
char label;
Map<Character, TrieNode> children = new HashMap<>();
boolean isEnd;
public TrieNode(){
}
public TrieNode(char label) {
this.label = label;
}
}
构建字典树以及插入方法
class Trie {
public TrieNode root;
public Trie(){
this.root = new TrieNode();
}
public void insert(String word){
if(word == null || word.length() == 0){
return;
}
TrieNode node = root;
for(int i=0;i<word.length();i++){
char ch = word.charAt(i);
if(!node.children.containsKey(ch)){
node.children.put(ch, new TrieNode(ch));
}
node = node.children.get(ch);
}
//注意在单词结尾isEnd = true
node.isEnd = true;
}
}
dfs代码实现
public class Solution {
/**
* @param board: A list of lists of character
* @param words: A list of string
* @return: A list of string
*/
class Position {
int x;
int y;
public Position(int x, int y){
this.x = x;
this.y = y;
}
}
//获得上下左右字符的位置
int[] dx = {-1, 1, 0, 0};
int[] dy = {0, 0, -1, 1};
public List<String> wordSearchII(char[][] board, List<String> words) {
// write your code here
List<String> results = new ArrayList<>();
if(board == null || board.length == 0 || board[0].length == 0|| words == null || words.size() == 0){
return results;
}
//build a trie for dictionary
Trie wordsTrie = new Trie();
for(String word: words){
wordsTrie.insert(word);
}
//find all possible words in board
boolean[][] visited = new boolean[board.length][board[0].length];
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
char ch = board[i][j];
if(!wordsTrie.root.children.containsKey(ch)) {
continue;
}
Position position = new Position(i,j);
StringBuilder prefix = new StringBuilder();
prefix.append(ch);
TrieNode currNode = wordsTrie.root.children.get(ch);
visited[i][j] = true;
dfs(board,
wordsTrie,
position,
currNode,
prefix,
visited,
results);
visited[i][j] = false;
}
}
return results;
}
//define: find all words generated from board in trie
private void dfs(char[][] board,
Trie trie,
Position position,
TrieNode currNode,
StringBuilder prefix,
boolean[][] visited,
List<String> results){
//output:the char reaches the end of a word
if(currNode.isEnd){
//把已经查找到的单词isEnd=false,防止重复查找
currNode.isEnd = false;
results.add(new String(prefix));
}
for(int i=0;i<4;i++){
Position nextPosition = new Position(position.x + dx[i],
position.y + dy[i]
);
//if the char exsits in board
if(!isBound(board, nextPosition)){
continue;
}
if(visited[nextPosition.x][nextPosition.y]){
continue;
}
char nextCh = board[nextPosition.x][nextPosition.y];
if(!currNode.children.containsKey(nextCh)){
continue;
}
TrieNode nextNode = currNode.children.get(nextCh);
//backtracking
prefix.append(nextCh);
visited[nextPosition.x][nextPosition.y] = true;
dfs(board, trie, nextPosition, nextNode, prefix, visited, results);
//回溯后状态复原
prefix.deleteCharAt(prefix.length() - 1);
visited[nextPosition.x][nextPosition.y] = false;
}
}
boolean isBound(char[][] board, Position pos){
if(pos.x < 0 || pos.x >= board.length || pos.y < 0 || pos.y >= board[0].length){
return false;
}
return true;
}
}