Expert: advanced patterns and interviews · Tries, union-find and bit manipulation · topic 47 of 61 · 35 min
Tries (prefix trees)
Explanation
The problem: questions about prefixes
When you type "ban" into a shopping app's search box, it suggests "banana", "bangles" and "bandage" before you finish. The question is not "is this exact word stored?" but "which stored words START with these letters?". A set answers the first question in O(L) time (hashing a word of length L), but it cannot answer the second without checking every word. With N words, scanning costs O(N × L) per keystroke.
What a trie is
A trie (from "retrieval", usually said like "try") is a tree where each edge stands for one character. The path from the root to a node spells a prefix. Each node has a children map (a dictionary from character to child node) and an end flag (often called is_end) that says "a complete word ends here".
Words that share a prefix share the same path. After inserting "car", "cat" and "do", the root has two children, c and d. The path c → a is shared by "car" and "cat", and only then splits into r and t.
(root)
├─ c
│ └─ a
│ ├─ r (end of "car")
│ └─ t (end of "cat")
└─ d
└─ o (end of "do")class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
root = TrieNode()
def insert(word):
node = root
for ch in word:
if ch not in node.children:
node.children[ch] = TrieNode()
node = node.children[ch]
node.is_end = True
for w in ["car", "cat", "do"]:
insert(w)
print(sorted(root.children)) # Output: ['c', 'd']
print(sorted(root.children["c"].children["a"].children)) # Output: ['r', 't']Search versus starts_with
Both operations walk down from the root, one character at a time. If a character is missing, the answer is False. The difference is at the end. search(word) needs is_end to be True at the last node, because "ca" is a path in the trie above but not a stored word. starts_with(prefix) only needs the walk to succeed: "ca" is a prefix of "car".
Cost
Insert, search and starts_with each visit at most L nodes for a word or prefix of length L, so each is O(L) time, and the number of stored words does not matter. Space is the catch. In the worst case every character of every word is its own node, so a trie holds up to O(total characters) nodes, and in Python each node is an object with its own dictionary. A trie of a million short words can use many times the memory of a set holding the same words. Compact versions use an array of 26 children for lowercase letters, or merge chains of single children into one edge (a radix tree).
Autocomplete
To suggest words for a prefix, first walk down to the prefix node (O(L)). Then run a DFS below it and collect every node whose is_end is True, building the word as you go. If you visit the children in sorted order, the words come out in alphabetical order, so you can stop as soon as you have k of them. Real search boxes rank suggestions by popularity or by what you searched before, often by keeping the top few suggestions at each node, but the walk-then-collect idea is the same.
Longest common prefix
The longest common prefix of a list of words is the longest start that all of them share: "fl" for "flower", "flow" and "flight". With a trie, insert all the words and walk down from the root while the current node has exactly one child and is not the end of a word. Without a trie there is a neat trick: the common prefix of all the words equals the common prefix of the alphabetically smallest and largest words.
words = ["flower", "flow", "flight"]
a, b = min(words), max(words)
i = 0
while i < len(a) and a[i] == b[i]:
i += 1
print(a[:i]) # Output: flCommon mistakes
- Forgetting the end flag, so search("ca") returns True after inserting only "car". Fix: set is_end at the last node of insert and check it in search.
- Using a mutable default argument such as def __init__(self, children={}), which makes every node share ONE dictionary. Fix: create self.children = {} inside __init__.
- Storing the trie as a class attribute, so two Trie objects share the same words. Fix: create the root in __init__.
- Collecting autocomplete results without sorting the children, then sorting a huge list at the end. Fix: visit children in sorted order and stop at k.
- Using a trie when only exact lookups are needed. Fix: a set is simpler and smaller.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Tries (prefix trees) · 1/8
This lesson is about tries, also called prefix trees: the structure behind search suggestions.
Prefer a video by a teacher? Find free videos on “trie data structure implement insert search startswith autocomplete” on YouTube ↗
In real life: Contact search on a phone
When you type "Ra" in your phone's contacts, the list shrinks to names starting with those letters. A trie keyed by letters is one way to do this: each keystroke moves one level down, and the names below the current node are the matches. Real apps also match surnames, nicknames and phone numbers, and rank frequent contacts first, but prefix lookup is the core.
class Node:
def __init__(self):
self.children = {}
self.is_end = False
root = Node()
for name in ["rahul", "rajesh", "ramya", "ravi", "rekha", "sana"]:
node = root
for ch in name:
node = node.children.setdefault(ch, Node())
node.is_end = True
def names_below(node, path, out):
if node.is_end:
out.append(path)
for ch in sorted(node.children):
names_below(node.children[ch], path + ch, out)
node = root
for ch in "ra":
node = node.children[ch]
found = []
names_below(node, "ra", found)
print(found)Practice (0/3 done)
Write a class Trie with three methods. insert(word) adds a word (returns None). search(word) returns True only if that exact word was inserted. starts_with(prefix) returns True if some inserted word starts with prefix; starts_with("") is always True. Each trie must keep its own words. Example: after insert("apple"), search("app") is False and starts_with("app") is True.
Write autocomplete(words, prefix, k) that returns up to k DIFFERENT words from words that start with prefix, in alphabetical order. Build a trie, walk to the prefix node, then collect words with a DFS that visits children in sorted order and stops after k. Example: autocomplete(["mobile", "mouse", "moneypot", "monitor", "mousepad"], "mou", 5) returns ["mouse", "mousepad"].
Write longest_common_prefix(words) that returns the longest string that every word in the list starts with. Return "" for an empty list or when the words share nothing. Example: longest_common_prefix(["flower", "flow", "flight"]) returns "fl".
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Union-find (disjoint set union)Tries, union-find and bit manipulation · same module
- Minimum spanning treesTries, union-find and bit manipulation · same module
- Bits: AND, OR, XOR and shiftsTries, union-find and bit manipulation · same module
- Prefix sums and difference arraysArrays and strings
- Binary search treesC Programming (from zero to systems) · Data structures in C
- Binary search treesTrees and binary search trees
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
A trie holds N words. What does search(word) cost for a word of length L?
Answer: O(L)
It walks at most L nodes; the number of stored words does not matter.
Why does each trie node need an end flag?
Answer: To tell a complete word apart from a prefix of a longer word
After inserting "car", the path c-a exists, but "ca" is not a word unless its node is marked.
You insert "tea" and "ten" into an empty trie. How many nodes are there, counting the root?
Answer: 5
root, t, e, then a and n: the prefix "te" is shared.
Which question does a plain set answer just as well as a trie?
Answer: Is "chai" one of the stored words?
Exact membership is O(L) in a set too; prefix questions are where tries win.
What is the main cost of a trie compared with a set of the same words?
Answer: More memory, since every node is an object with its own map
Many small node objects add up; compressed tries reduce this.
Why is searching down one path O(log n) in a balanced tree but O(n) in a skewed one?Related
Answer: The cost is O(height), and the height is about log n when balanced but n − 1 when skewed
Walking down one path costs one step per level, and the number of levels is the height.
How much extra space does "first unique character" need for a string of lowercase English letters?Related
Answer: O(1), at most 26 counts
The count map has at most one entry per letter, and there are only 26 letters.
n items and q range-sum queries. What is the total cost with a prefix array?Related
What is the worst-case search time in an unbalanced BST?Related
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 4 more topics in “Tries, union-find and bit manipulation” before its exam: Union-find (disjoint set union), Minimum spanning trees, Bits: AND, OR, XOR and shifts, Bit tricks in interviews.
- Module 10 exam: Tries, union-find and bits · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Dynamic programming. 61 topics left in the course before the final exam.