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
- 1Check whether many queries share prefixes or whether a search can be pruned by prefix.
- 2Choose the child structure: a map for general alphabets, a fixed array of 26 for lowercase letters.
- 3Decide what each node stores beyond children (end flag, count, word).
- 4Write insert and walk first; build every query on the walk.
- 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 NoneVariants
- 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
| Operation | Cost |
|---|---|
| Insert / search / prefix check | O(L) for a word of length L |
| Space for the trie | O(total characters × alphabet) worst case |
| Wildcard search | Up to O(alphabet^L) in the worst case, much less in practice |
| Rolling hash substring compare | O(1) after O(n) preprocessing |
23 Tries & Strings problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Prefix RegistryFreeEasy
- 2Longest Shared OpeningFreeEasy
- 3Same Letter BagFreeEasy
- 4Consistent Letter RelabelEasy
- 5Rotation of Each OtherEasy
- 6Mirror After One CutEasy
- 7Built From One BlockEasy
- 8Prefix Popularity CountsMedium
- 9Shortest Root SubstitutionMedium
- 10Anagram ClustersMedium
- 11Count Mirror SubstringsMedium
- 12Every Pattern OccurrenceMedium
- 13Wildcard Word RegistryMedium
- 14Scrambled Pattern WindowsMedium
- 15Typeahead Top ThreeMedium
- 16Longest Stepwise WordMedium
- 17Strongest XOR PairMedium
- 18Prefix Echo ScoreMedium
- 19Grid Word HuntHard
- 20Longest Echoed FragmentHard
- 21Shortest Mirror by PrependingHard
- 22Mirror Pair IndicesHard
- 23Compound Word FinderHard
Solve them in the browser, in Python or JavaScript.