Advanced Trie Interview Patterns: Word Search, Autocomplete, and XOR Maximum (2025)

Trie Fundamentals and Implementation

A trie (prefix tree) stores strings character by character, sharing prefixes among strings. Each node represents one character; the path from root to a node spells a prefix. A node marked as end_of_word indicates a complete word ends there. Time complexity: insert, search, startsWith — all O(L) where L is the string length. Space: O(total characters across all words) — efficient when many strings share prefixes. Use cases: autocomplete (find all words with a prefix), spell checking, IP routing (longest prefix match), word search in a grid, XOR maximum (binary trie).

class TrieNode:
    def __init__(self):
        self.children = {}  # char -> TrieNode
        self.is_end = False
        self.count = 0  # how many words pass through this node

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
            node.count += 1
        node.is_end = True

    def search(self, word: str) -> bool:
        node = self._find(word)
        return node is not None and node.is_end

    def starts_with(self, prefix: str) -> bool:
        return self._find(prefix) is not None

    def _find(self, prefix: str):
        node = self.root
        for c in prefix:
            if c not in node.children:
                return None
            node = node.children[c]
        return node

Word Search II (LC 212) — DFS + Trie

Find all words in a 2D grid. Naive: run DFS from each cell for each word. With Trie: insert all words into a trie. DFS from each cell, following the trie — prune when no trie path matches. Pruning: when a trie node has no children, no word can extend from this path.

def find_words(board: list[list[str]], words: list[str]) -> list[str]:
    trie = Trie()
    for w in words:
        trie.insert(w)

    rows, cols = len(board), len(board[0])
    result = set()

    def dfs(node, r, c, path):
        if node.is_end:
            result.add(path)
        board[r][c], temp = '#', board[r][c]  # mark visited
        for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
            nr, nc = r + dr, c + dc
            if 0 <= nr < rows and 0 <= nc < cols:
                ch = board[nr][nc]
                if ch != '#' and ch in node.children:
                    dfs(node.children[ch], nr, nc, path + ch)
        board[r][c] = temp  # restore

    for r in range(rows):
        for c in range(cols):
            ch = board[r][c]
            if ch in trie.root.children:
                dfs(trie.root.children[ch], r, c, ch)
    return list(result)

Design Search Autocomplete System (LC 642)

Each trie node stores the top-3 hottest sentences (by frequency) that pass through it. On insert: update frequencies, propagate top-3 up the trie. On search prefix: traverse to the prefix node, return its stored top-3.

import heapq

class AutocompleteSystem:
    def __init__(self, sentences: list[str], times: list[int]):
        self.root = TrieNode()
        self.freq = {}
        self.prefix = ""
        for s, t in zip(sentences, times):
            self.freq[s] = t
            self._add(s, t)

    def _add(self, sentence: str, freq: int):
        node = self.root
        for c in sentence:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
            # Maintain top-3 by frequency
            node.top = sorted(
                set(node.top + [(freq, sentence)]),
                key=lambda x: (-x[0], x[1])
            )[:3]

    def input(self, c: str) -> list[str]:
        if c == '#':
            self.freq[self.prefix] = self.freq.get(self.prefix, 0) + 1
            self._add(self.prefix, self.freq[self.prefix])
            self.prefix = ""
            return []
        self.prefix += c
        node = self.root
        for ch in self.prefix:
            if ch not in node.children:
                return []
            node = node.children[ch]
        return [s for _, s in node.top]

XOR Maximum with Binary Trie (LC 421)

Maximum XOR of two numbers in an array. Insert all numbers into a binary trie (bit by bit, from MSB to LSB). For each number, greedily find the number in the trie that maximizes XOR (always try to take the opposite bit).

def find_maximum_xor(nums: list[int]) -> int:
    # Build binary trie
    root = [None, None]  # [bit=0 child, bit=1 child]
    for n in nums:
        node = root
        for i in range(31, -1, -1):
            bit = (n >> i) & 1
            if node[bit] is None:
                node[bit] = [None, None]
            node = node[bit]

    max_xor = 0
    for n in nums:
        node = root
        curr_xor = 0
        for i in range(31, -1, -1):
            bit = (n >> i) & 1
            want = 1 - bit  # prefer opposite bit for max XOR
            if node[want] is not None:
                curr_xor |= (1 << i)
                node = node[want]
            else:
                node = node[bit]
        max_xor = max(max_xor, curr_xor)
    return max_xor

See also: Meta Interview Prep

See also: Atlassian Interview Prep

See also: Databricks Interview Prep

Scroll to Top