Problem

Given a board of characters and a list of words, return all words that can be formed by traversing adjacent cells (no reuse).

Example

Input:  board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]], words = ["oath","pea","eat","rain"]
Output: ["eat","oath"]

Solution

Build a Trie of the words. DFS each cell against the Trie. Mark cells visited with #.

def find_words(board, words):
    trie = {}
    for w in words:
        node = trie
        for ch in w: node = node.setdefault(ch, {})
        node['#'] = w
    result = []
    def dfs(r, c, node):
        ch = board[r][c]
        if ch not in node: return
        next_node = node[ch]
        if '#' in next_node:
            result.append(next_node['#'])
            del next_node['#']
        board[r][c] = '*'
        for dr, dc in [(0,1),(0,-1),(1,0),(-1,0)]:
            nr, nc = r + dr, c + dc
            if 0 <= nr < len(board) and 0 <= nc < len(board[0]):
                dfs(nr, nc, next_node)
        board[r][c] = ch
    for r in range(len(board)):
        for c in range(len(board[0])):
            dfs(r, c, trie)
    return result
function findWords(board, words) {
    const trie = {};
    for (const w of words) {
        let node = trie;
        for (const ch of w) { if (!node[ch]) node[ch] = {}; node = node[ch]; }
        node['#'] = w;
    }
    const result = [];
    function dfs(r, c, node) {
        const ch = board[r][c];
        if (!node[ch]) return;
        const next = node[ch];
        if (next['#']) { result.push(next['#']); delete next['#']; }
        board[r][c] = '*';
        for (const [dr, dc] of [[0,1],[0,-1],[1,0],[-1,0]]) {
            const nr = r + dr, nc = c + dc;
            if (nr >= 0 && nr < board.length && nc >= 0 && nc < board[0].length) dfs(nr, nc, next);
        }
        board[r][c] = ch;
    }
    for (let r = 0; r < board.length; r++)
        for (let c = 0; c < board[0].length; c++) dfs(r, c, trie);
    return result;
}
struct Trie {
    Trie* children[26] = {nullptr};
    string word = "";
};
void dfs(vector<vector<char>>& board, int r, int c, Trie* node, vector<string>& result) {
    char ch = board[r][c];
    if (ch == '*' || !node->children[ch - 'a']) return;
    Trie* next = node->children[ch - 'a'];
    if (!next->word.empty()) {
        result.push_back(next->word);
        next->word = "";
    }
    board[r][c] = '*';
    int dr[] = {-1, 1, 0, 0}, dc[] = {0, 0, -1, 1};
    for (int d = 0; d < 4; d++) {
        int nr = r + dr[d], nc = c + dc[d];
        if (nr >= 0 && nr < board.size() && nc >= 0 && nc < board[0].size()) dfs(board, nr, nc, next, result);
    }
    board[r][c] = ch;
}
vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {
    Trie* root = new Trie();
    for (auto& w : words) {
        Trie* node = root;
        for (char ch : w) {
            if (!node->children[ch - 'a']) node->children[ch - 'a'] = new Trie();
            node = node->children[ch - 'a'];
        }
        node->word = w;
    }
    vector<string> result;
    for (int r = 0; r < board.size(); r++)
        for (int c = 0; c < board[0].size(); c++) dfs(board, r, c, root, result);
    return result;
}
class TrieNode { TrieNode[] children = new TrieNode[26]; String word = null; }
public List<String> findWords(char[][] board, String[] words) {
    TrieNode root = new TrieNode();
    for (String w : words) {
        TrieNode node = root;
        for (char ch : w.toCharArray()) {
            if (node.children[ch - 'a'] == null) node.children[ch - 'a'] = new TrieNode();
            node = node.children[ch - 'a'];
        }
        node.word = w;
    }
    List<String> result = new ArrayList<>();
    for (int r = 0; r < board.length; r++)
        for (int c = 0; c < board[0].length; c++) dfs(board, r, c, root, result);
    return result;
}
private void dfs(char[][] b, int r, int c, TrieNode node, List<String> result) {
    char ch = b[r][c];
    if (ch == '*' || node.children[ch - 'a'] == null) return;
    TrieNode next = node.children[ch - 'a'];
    if (next.word != null) { result.add(next.word); next.word = null; }
    b[r][c] = '*';
    int[] dr = {-1, 1, 0, 0}, dc = {0, 0, -1, 1};
    for (int d = 0; d < 4; d++) {
        int nr = r + dr[d], nc = c + dc[d];
        if (nr >= 0 && nr < b.length && nc >= 0 && nc < b[0].length) dfs(b, nr, nc, next, result);
    }
    b[r][c] = ch;
}

Complexity

  • Time: O(M(4ยท3^(L-1)))
  • Space: O(N)

Explanation

Trie shares prefixes across multiple words. We can prune entire subtrees the moment a path doesn’t match any prefix.

Share this article

Comments

Join the discussion. Got a question, found an issue, or want to share your experience?

Leave a Comment

Your email stays private. We just use it for replies.

Nothing to preview yet.

Use **bold**, *italic*, `code`, ```code blocks```, [link](url), > quote, - list