import java.util.ArrayList;
import java.util.List;
/**
* Given a 2D board and a list of words from the dictionary, find all words in the board.
* <p>
* Each word must be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those
* horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.
* <p>
* For example,
* Given words = ["oath","pea","eat","rain"] and board =
* <p>
* [
* ['o','a','a','n'],
* ['e','t','a','e'],
* ['i','h','k','r'],
* ['i','f','l','v']
* ]
* Return ["eat","oath"].
* Note:
* You may assume that all inputs are consist of lowercase letters a-z.
* <p>
* You would need to optimize your backtracking to pass the larger test. Could you stop backtracking earlier?
* <p>
* If the current candidate does not exist in all words' prefix, you could stop backtracking immediately. What kind of
* data structure could answer such query efficiently? Does a hash table work? Why or why not? How about a Trie? If you
* would like to learn how to implement a basic trie, please work on this problem: Implement Trie (Prefix Tree) first.
* <p>
* Tags: Backtracking, Trie
* Similar Problems: (M) Word Search
*/
public class WordSearch2 {
/**
* Build a trie for the words
* When backtracking, move forward with the trie node
* Do pruning when the current char is not in the trie
*/
public List<String> findWords(char[][] board, String[] words) {
List<String> res = new ArrayList<>();
TrieNode root = buildTrie(words);
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length; j++) {
dfs(board, i, j, root, res);
}
}
return res;
}
/**
* Build a trie from the words
* Store word in the ending node
*/
private TrieNode buildTrie(String[] words) {
TrieNode root = new TrieNode();
for (String w : words) {
TrieNode node = root;
for (char c : w.toCharArray()) {
int i = c - 'a';
if (node.next[i] == null) node.next[i] = new TrieNode();
node = node.next[i];
}
node.word = w;
}
return root;
}
/**
* Backtrack in the board
* Set a character to # to mark it as visited
* Remember to reset it after all 4 adjacent nodes are traversed
* <p>
* Get current char in board
* Make sure it's not already visited and it's in trie
* Move trie node one step down to process current word
* Set word in node to null after adding it to the result list
* Set visited and backtrack adjacent nodes
* Reset visited mark
*/
private void dfs(char[][] board, int i, int j, TrieNode node, List<String> res) {
char c = board[i][j];
if (c == '#' || node.next[c - 'a'] == null) return;
node = node.next[c - 'a'];
if (node.word != null) { // Found one
res.add(node.word);
node.word = null; // De-dup
}
board[i][j] = '#'; // Mark as visited
if (i > 0) dfs(board, i - 1, j, node, res);
if (j > 0) dfs(board, i, j - 1, node, res);
if (i < board.length - 1) dfs(board, i + 1, j, node, res);
if (j < board[i].length - 1) dfs(board, i, j + 1, node, res);
board[i][j] = c; // Reset mark
}
class TrieNode {
TrieNode[] next = new TrieNode[26];
String word;
}
}