Pattern 08 of 20

Tries & Strings

A prefix tree for words: shared prefixes stored once, prefix queries in O(length).

When to use it

  • Many queries ask whether a word or a prefix exists
  • Autocomplete, word search over a grid, dictionaries of words
  • String problems where comparing whole words is too slow

The idea

Each node is a character and each path from the root spells a prefix. Inserting and looking up cost the length of the word, independent of how many words are stored, and a search can stop as soon as a prefix is missing.

Typical cost O(L) per insert or lookup, O(total characters) space.

How it works

A trie stores a set of strings as a tree of characters. Every node is a prefix, and its children are the possible next characters. Words that share a beginning share the same path, so the dictionary is compressed and every prefix query is answered by walking at most L nodes.

The power is early stopping. When a search over a grid or a sentence reaches a prefix that no word has, the trie says so immediately and the whole branch is abandoned. That pruning is why word search with many target words uses a trie rather than searching each word separately.

Mark the end of a word with a flag on the node, not with the absence of children: 'car' and 'card' both live on the same path. Nodes can also carry extra data, such as a count of words passing through, the stored word itself, or the best suggestion.

Beyond tries, this topic covers string techniques that compare without rescanning: rolling hashes, prefix-function matching, and palindrome expansion. All share the goal of avoiding repeated character-by-character comparisons.

How to attack it

  1. 1Check whether many queries share prefixes or whether a search can be pruned by prefix.
  2. 2Choose the child structure: a map for general alphabets, a fixed array of 26 for lowercase letters.
  3. 3Decide what each node stores beyond children (end flag, count, word).
  4. 4Write insert and walk first; build every query on the walk.
  5. 5For grid searches, remove or mark found words to avoid duplicates and speed up later searches.

Template

A skeleton to adapt, not a solution to paste. Write it from memory a few times.

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


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

    def insert(self, word):
        node = self.root
        for ch in word:
            node = node.children.setdefault(ch, TrieNode())
            node.count += 1
        node.is_word = True

    def _walk(self, prefix):
        # Returns the node for prefix, or None as soon as a char is missing.
        node = self.root
        for ch in prefix:
            node = node.children.get(ch)
            if node is None:
                return None
        return node

    def search(self, word):
        node = self._walk(word)
        return node is not None and node.is_word

    def starts_with(self, prefix):
        return self._walk(prefix) is not None

Variants

Prefix counting
Store a pass-through count on each node to answer 'how many words start with p' in O(|p|).
Wildcard search
A '.' that matches any character becomes a DFS over all children at that position.
Trie-guided grid search
Run DFS on the grid while walking the trie in step, stopping as soon as the current path is not a prefix.
Bitwise trie
Insert numbers bit by bit from the highest; greedily choosing the opposite bit finds the maximum XOR pair.
Rolling hash
Compare substrings in O(1) after O(n) preprocessing; verify on collision when correctness matters.

Pitfalls

  • Treating a node with no children as the end of a word, which loses words that are prefixes of other words.
  • Rebuilding substrings during DFS, which adds an O(L) copy at every step.
  • Reporting the same word twice in a grid search; clear the flag once found.
  • Choosing a 26-slot array when the alphabet includes uppercase, digits or Unicode.
  • Forgetting that memory grows with total characters, not with the number of words.

What it costs

OperationCost
Insert / search / prefix checkO(L) for a word of length L
Space for the trieO(total characters × alphabet) worst case
Wildcard searchUp to O(alphabet^L) in the worst case, much less in practice
Rolling hash substring compareO(1) after O(n) preprocessing

23 Tries & Strings problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1Prefix RegistryFreeEasy
  2. 2Longest Shared OpeningFreeEasy
  3. 3Same Letter BagFreeEasy
  4. 4Consistent Letter RelabelEasy
  5. 5Rotation of Each OtherEasy
  6. 6Mirror After One CutEasy
  7. 7Built From One BlockEasy
  8. 8Prefix Popularity CountsMedium
  9. 9Shortest Root SubstitutionMedium
  10. 10Anagram ClustersMedium
  11. 11Count Mirror SubstringsMedium
  12. 12Every Pattern OccurrenceMedium
  13. 13Wildcard Word RegistryMedium
  14. 14Scrambled Pattern WindowsMedium
  15. 15Typeahead Top ThreeMedium
  16. 16Longest Stepwise WordMedium
  17. 17Strongest XOR PairMedium
  18. 18Prefix Echo ScoreMedium
  19. 19Grid Word HuntHard
  20. 20Longest Echoed FragmentHard
  21. 21Shortest Mirror by PrependingHard
  22. 22Mirror Pair IndicesHard
  23. 23Compound Word FinderHard

Solve them in the browser, in Python or JavaScript.