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;
    }
}

add Word Search 2