19. Tries#
Theory#
A trie (prefix tree) stores strings character-by-character. Shared prefixes are stored once, which makes prefix search and autocomplete efficient.
See the full write-up: Data Structures - Trie.
When FAANG asks: autocomplete, prefix search, word dictionary with wildcards (LC 211), or grid + DFS with a trie (LC 212 Word Search II). Prefer a hash set of prefixes when you only need exact words; use a trie when you need prefix completion or shared-prefix compression.
| Use trie | Use hash set / dict instead |
|---|---|
Prefix matching (startsWith) |
Exact word lookup only |
| Autocomplete / top-k suggestions | One-off membership checks |
| Word Search II (prune dead paths early) | Small, fixed word list on a grid |
Wildcard search with . (LC 211) |
No prefix or wildcard queries |
| Binary trie for max XOR (LC 421) | — |
Problems at a glance#
| LC | Problem | |
|---|---|---|
| 208 | Implement Trie (Prefix Tree) | ↗ |
| 720 | Longest Word in Dictionary | ↗ |
| 677 | Map Sum Pairs | ↗ |
| 212 | Word Search II | ↗ |
| 648 | Replace Words | ↗ |
| 211 | Add and Search Word | ↗ |
| 421 | Maximum XOR of Two Numbers in an Array | ↗ |
| 1268 | Search Suggestions System | ↗ |
| 2255 | Count Prefixes of a Given String | ↗ |
Problems#
Problem 1: Implement Trie (Prefix Tree) (Leetcode:208)#
Problem Statement
A trie (pronounced as "try") or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.
Implement the Trie class:
Trie()Initializes the trie object.void insert(String word)Inserts the stringwordinto the trie.boolean search(String word)Returnstrueif the stringwordis in the trie (i.e., was inserted before), andfalseotherwise.boolean startsWith(String prefix)Returnstrueif there is a previously inserted stringwordthat has the prefixprefix, andfalseotherwise.
Example 1:
Input
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
Output
[null, null, true, false, true, null, true]Explanation
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // return True
trie.search("app"); // return False
trie.startsWith("app"); // return True
trie.insert("app");
trie.search("app"); // return True
Constraints:
1 <= word.length, prefix.length <= 2000wordandprefixconsist only of lowercase English letters.- At most
3 * 104calls in total will be made toinsert,search, andstartsWith.
Code and Explanation
Each node stores a map of child characters. Walk the trie for every operation:
- Insert — follow/create edges for each character; mark the last node as a word end.
- Search — follow edges; return true only if the path exists and
is_endis set. - startsWith — same walk, but any existing prefix path is enough (no
is_endcheck).
A shared helper _find keeps search and prefix logic DRY.
Time: O(L) per operation · Space: O(total characters stored)
When the alphabet is fixed (26 lowercase letters), an array of size 26 per node avoids hash-map overhead and gives O(1) child lookup via index. Same logic as Approach 1 — trade a bit more memory for faster constant factors.
Time: O(L) per operation · Space: O(26 · nodes) worst case
Problem 2: Longest Word in Dictionary (Leetcode:720)#
Problem Statement
Given an array of strings words representing an English Dictionary, return the longest word in words that can be built one character at a time by other words in words.
If there is more than one possible answer, return the longest word with the smallest lexicographical order. If there is no answer, return the empty string.
Note that the word should be built from left to right with each additional character being added to the end of a previous word.
Example 1:
Input: words = ["w","wo","wor","worl","world"]
Output: "world"
Explanation: The word "world" can be built one character at a time by "w", "wo", "wor", and "worl".
Example 2:
Input: words = ["a","banana","app","appl","ap","apply","apple"]
Output: "apple"
Explanation: Both "apply" and "apple" can be built from other words in the dictionary. However, "apple" is lexicographically smaller than "apply".
Constraints:
1 <= words.length <= 10001 <= words[i].length <= 30words[i]consists of lowercase English letters.
Code and Explanation
Build a trie of all words. A word is "buildable" iff every prefix along its path is also marked
is_end. DFS from the root, visiting children in sorted order (for lex tie-breaking), only continuing through nodes that end a valid prefix.
- Insert every word into the trie.
- DFS from root with empty path.
- At each
is_endnode, update the best candidate and recurse to children.
Time: O(N · L + N log N) · Space: O(N · L)
Sort words by descending length, then lex order. The first word whose every proper prefix exists in a hash set is the answer. No trie needed — prefix checks are O(L) hash lookups — but sorting upfront handles the tie-breaking.
Time: O(N · L log N) · Space: O(N · L)
Problem 3: Map Sum Pairs (Leetcode:677)#
Problem Statement
Design a map that allows you to do the following:
- Maps a string key to a given value.
- Returns the sum of the values that have a key with a prefix equal to a given string.
Implement the MapSum class:
MapSum()Initializes theMapSumobject.void insert(String key, int val)Inserts thekey-valpair into the map. If thekeyalready existed, the originalkey-valuepair will be overridden to the new one.int sum(string prefix)Returns the sum of all the pairs' value whosekeystarts with theprefix.
Example 1:
Input
["MapSum", "insert", "sum", "insert", "sum"]
[[], ["apple", 3], ["ap"], ["app", 2], ["ap"]]
Output
[null, null, 3, null, 5]Explanation
MapSum mapSum = new MapSum();
mapSum.insert("apple", 3);
mapSum.sum("ap"); // return 3 (apple = 3)
mapSum.insert("app", 2);
mapSum.sum("ap"); // return 5 (apple + app = 3 + 2 = 5)
Constraints:
1 <= key.length, prefix.length <= 50keyandprefixconsist of only lowercase English letters.1 <= val <= 1000- At most
50calls will be made toinsertandsum.
Code and Explanation
Store a running sum at every trie node. When inserting, compute the delta from the old value (0 if new key) and add it along the key's path.
sum(prefix) is then a single O(L) walk to the prefix node.
- Track previous values in a hash map for override handling.
- On insert, propagate
deltadown the trie path. - On sum, return
node.sumat the prefix endpoint.
Time: O(L) per insert/sum · Space: O(total key characters)
Store key→value in a hash map and scan all keys on each
sum call. Simple and fine for tiny inputs (≤ 50 calls here), but O(K · L) per query vs O(L) with a trie.
Time: O(K · L) per sum · Space: O(K)
Problem 4: Word Search II (Leetcode:212)#
Problem Statement
Given an m x n board of characters and a list of strings words, return all words on the board.
Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.
Example 1:
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"]
Example 2:
Input: board = [["a","b"],["c","d"]], words = ["abcb"]
Output: []
Constraints:
m == board.lengthn == board[i].length1 <= m, n <= 12board[i][j]is a lowercase English letter.1 <= words.length <= 3 * 1041 <= words[i].length <= 10words[i]consists of lowercase English letters.- All the strings of
wordsare unique.
Code and Explanation
Insert all words into a trie. DFS from every cell, walking the trie in sync with the board path. Key optimizations:
- Store the full word at end nodes — collect matches immediately.
- Mark visited cells with
#, restore on backtrack. - Prune trie branches once exhausted — avoids revisiting dead paths.
The trie lets you share prefixes across words and bail out as soon as no trie child matches the next letter.
Time: O(M · N · 4^L) with heavy pruning · Space: O(total word chars)
Store words in a hash set and backtrack, checking set membership for every path built. Works for small word lists but explores many paths that can never lead to a word — no early prefix pruning. The trie version is the interview answer for large
words arrays.
Time: O(M · N · 4^L · L) · Space: O(W · L)
Problem 5: Replace Words (Leetcode:648)#
Problem Statement
In English, we have a concept called root, which can be followed by some other word to form another longer word - let's call this word derivative. For example, when the root "help" is followed by the word "ful", we can form a derivative "helpful".
Given a dictionary consisting of many roots and a sentence consisting of words separated by spaces, replace all the derivatives in the sentence with the root forming it. If a derivative can be replaced by more than one root, replace it with the root that has the shortest length.
Return the sentence after the replacement.
Example 1:
Input: dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery"
Output: "the cat was rat by the bat"
Example 2:
Input: dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs"
Output: "a a b c"
Constraints:
1 <= dictionary.length <= 10001 <= dictionary[i].length <= 100dictionary[i]consists of only lower-case letters.1 <= sentence.length <= 106sentenceconsists of only lower-case letters and spaces.- The number of words in
sentenceis in the range[1, 1000]- The length of each word in
sentenceis in the range[1, 1000]- Every two consecutive words in
sentencewill be separated by exactly one space.sentencedoes not have leading or trailing spaces.
Code and Explanation
Insert all roots into a trie. For each sentence word, walk the trie character by character. The first
is_end node encountered is the shortest matching root (trie depth = prefix length). If no root matches, keep the original word.
Time: O(D · L + W · L) · Space: O(D · L)
Check every prefix of each word against a hash set of roots. Return the shortest match. Correct and simple; each prefix check is O(L) for hashing. The trie wins when roots share long prefixes (fewer redundant comparisons).
Time: O(W · L²) · Space: O(D · L)
Problem 6: Add and Search Word (Leetcode:211)#
Problem Statement
Design a data structure that supports adding new words and finding if a string matches any previously added string.
Implement the WordDictionary class:
WordDictionary()Initializes the object.void addWord(word)Addswordto the data structure, it can be matched later.bool search(word)Returnstrueif there is any string in the data structure that matcheswordorfalseotherwise.wordmay contain dots'.'where dots can be matched with any letter.
Example:
Input
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
Output
[null,null,null,null,false,true,true,true]
Explanation
WordDictionary wordDictionary = new WordDictionary();
wordDictionary.addWord("bad");
wordDictionary.addWord("dad");
wordDictionary.addWord("mad");
wordDictionary.search("pad"); // return False
wordDictionary.search("bad"); // return True
wordDictionary.search(".ad"); // return True
wordDictionary.search("b.."); // return True
Constraints:
1 <= word.length <= 25wordinaddWordconsists of lowercase English letters.wordinsearchconsist of'.'or lowercase English letters.- There will be at most
2dots inwordforsearchqueries.- At most
104calls will be made toaddWordandsearch.
Code and Explanation
Insert words into a trie normally. For search, DFS through the trie:
- At a literal character, follow that single child.
- At
'.', try all children (backtracking). - At end of pattern, require
is_end.
With at most 2 dots, branching stays manageable. The trie structure avoids scanning every stored word.
Time: O(L) add · O(26^d · L) search with d wildcards · Space: O(total chars)
Store words in a list and compare each one character-by-character, treating
. as a wildcard. Simple but O(N · L) per search. The trie + DFS approach scales better as the dictionary grows.
Time: O(L) add · O(N · L) search · Space: O(N · L)
Problem 7: Maximum XOR of Two Numbers in an Array (Leetcode:421)#
Problem Statement
Given an integer array nums, return the maximum result of nums[i] XOR nums[j], where 0 <= i <= j < n.
Example 1:
Input: nums = [3,10,5,25,2,8]
Output: 28
Explanation: The maximum result is 5 XOR 25 = 28.
Example 2:
Input: nums = [14,70,53,83,49,91,36,80,92,51,66,70]
Output: 127
Constraints:
1 <= nums.length <= 2 * 1050 <= nums[i] <= 231 - 1
Code and Explanation
A binary trie stores each number's bits from MSB to LSB. To maximize XOR with a given number, greedily pick the opposite bit at each trie level when available.
- Insert all numbers bit-by-bit into the trie.
- For each number, walk the trie preferring the opposite bit.
- Track the maximum XOR found.
This is the classic trie variant — same prefix-tree idea, but branches are 0 and 1.
Time: O(N · B) where B = 32 · Space: O(N · B)
Try every pair and track the max XOR. O(N²) — only viable for tiny arrays. The binary trie reduces this to O(N · B) and is the intended solution at scale.
Time: O(N²) · Space: O(1)
Problem 8: Search Suggestions System (Leetcode:1268)#
Problem Statement
You are given an array of strings products and a string searchWord.
Design a system that suggests at most three product names from products after each character of searchWord is typed. Suggested products should have common prefix with searchWord. If there are more than three products with a common prefix return the three lexicographically minimums products.
Return a list of lists of the suggested products after each character of searchWord is typed.
Example 1:
Input: products = ["mobile","mouse","moneypot","monitor","mousepad"], searchWord = "mouse"
Output: [["mobile","moneypot","monitor"],["mobile","moneypot","monitor"],["mouse","mousepad"],["mouse","mousepad"],["mouse","mousepad"]]
Explanation: products sorted lexicographically = ["mobile","moneypot","monitor","mouse","mousepad"].
After typing m and mo all products match and we show user ["mobile","moneypot","monitor"].
After typing mou, mous and mouse the system suggests ["mouse","mousepad"].
Example 2:
Input: products = ["havana"], searchWord = "havana"
Output: [["havana"],["havana"],["havana"],["havana"],["havana"],["havana"]]
Explanation: The only word "havana" will be always suggested while typing the search word.
Constraints:
1 <= products.length <= 10001 <= products[i].length <= 30001 <= sum(products[i].length) <= 2 * 104- All the strings of
productsare unique.products[i]consists of lowercase English letters.1 <= searchWord.length <= 1000searchWordconsists of lowercase English letters.
Code and Explanation
Sort products lexicographically, then insert into a trie. At each node along the insert path, append the product to a
suggestions list (cap at 3). Because products are inserted in sorted order, each node's list is automatically the three lexicographically smallest matches for that prefix.
Typing searchWord is just walking the trie one character at a time.
Time: O(P · L + S) · Space: O(P · L)
Sort products once. For each typed character, binary-search the start of the prefix range, then scan forward (at most 3 items). No trie needed, but each step does O(log P + 3) work. The trie precomputes suggestions for O(1) lookup per character.
Time: O(P log P + S log P) · Space: O(P)
Problem 9: Count Prefixes of a Given String (Leetcode:2255)#
Problem Statement
You are given a string array words and a string s, where words[i] and s comprise only of lowercase English letters.
Return the number of strings in words that are a prefix of s.
A prefix of a string is a substring that occurs at the beginning of the string. A substring is a contiguous sequence of characters within a string.
Example 1:
Input: words = ["a","b","c","ab","bc","abc"], s = "abc"
Output: 3
Explanation:
The strings in words which are a prefix of s = "abc" are:
"a", "ab", and "abc".
Thus the number of strings in words which are a prefix of s is 3.
Example 2:
Input: words = ["a","a"], s = "aa"
Output: 2
Explanation:
Both of the strings are a prefix of s.
Note that the same string can occur multiple times in words, and it should be counted each time.
Constraints:
1 <= words.length <= 10001 <= words[i].length, s.length <= 10words[i]andsconsist of lowercase English letters only.
Code and Explanation
Insert each word into a trie, incrementing
count at the terminal node (handles duplicates). Walk s character by character through the trie, adding each node's count along the way. Each increment represents one word in words that is a prefix of s.
Time: O(W · L + |s|) · Space: O(W · L)
For each word, check if
s starts with it using built-in startswith. With tiny constraints (length ≤ 10), this one-liner is perfectly fine. The trie approach shines when you query many different strings against the same word list.
Time: O(W · L) · Space: O(1)

