Trie
Master this topic with zero to advance depth.
Trie: The Prefix Powerhouse
A Trie (pronounced "try"), also known as a Prefix Tree, is a specialized tree-based data structure used for efficient retrieval of keys in a large dataset of strings. Unlike a standard binary search tree, no node in the trie stores the key associated with that node; instead, its position in the tree defines the key with which it is associated.
1. Core Operations & Complexity
Trie operations are highly efficient, with time complexity scales with the length of the string (), not the number of keys ().
| Operation | Description | Time Complexity | Space Complexity |
|---|---|---|---|
Insert(word) | Add a word to the trie. | worst case | |
Search(word) | Check if a word exists in the trie. | ||
StartsWith(pre) | Check if any word starts with prefix pre. |
2. Why Use a Trie Over a Hash Map?
While Hash Maps offer search on average, Tries provide unique advantages:
- Prefix Searching: Tries can find all keys with a common prefix in time ( is the number of matches).
- Ordered Traversal: Keys can be retrieved in alphabetical order easily.
- Space Efficiency: Tries can save space when many keys share long common prefixes.
- No Hash Collisions: Tries avoid the overhead and worst-case scenarios of hash tables.
3. Implementation Patterns
Array vs. HashMap for Children
- Array
[26]: Fastest access ( to find child), but space-intensive if the alphabet is large or the trie is sparse. - HashMap: More space-efficient for sparse trees or large character sets (e.g., Unicode), but slightly slower due to hashing overhead.
4. Key Signals in Interviews:
- Autocomplete/Typeahead: Suggesting words as the user types.
- Longest Common Prefix: Finding the shared beginning of multiple strings.
- Wildcard Matching: Searching for words with missing characters (e.g.,
s.r). - Bitwise XOR: Binary Tries (storing bits) are used to find maximum XOR pairs in per query.
Implement Trie (Prefix Tree)
Implement a trie which supports insert, search, and startsWith operations.
Examples
Level I: HashMap-based Trie
Intuition
Flexible implementation using a HashMap for children, suitable for any character set (Unicode).
Detailed Dry Run
Insert 'apple': R -> a -> p -> p -> l -> e (end).
Level III: Array-based Trie (Optimal)
Intuition
Static array [26] for lowercase English. Fastest possible access with minimal memory overhead for dense tries.
Detailed Dry Run
Insert 'cat': root.children['c'-'a'] -> node1.children['a'-'a'] -> node2.children['t'-'a'] -> node3(isEnd=true).
Add and Search Word
Design a data structure that supports adding new words and finding if a string matches any previously added string, including wildcard character '.' matching.
Examples
Level III: Trie + DFS (Optimal)
Intuition
Standard Trie implementation. When encountering '.', perform DFS to check all possible children of the current node.
Detailed Dry Run
Search '.ad': Root -> dfs('.'). Try 'a'-'z'. 'b' exists. From 'b', dfs('ad') -> matches 'ad' and isEnd=true. Return true.
Map Sum Pairs
Calculate sum of all values whose key starts with a specific prefix.
Level III: Trie with Pre-calculated Sums (Optimal)
Intuition
Each trie node stores the sum of values for all words passing through it. When updating a key, we first find its old value to calculate the delta and update all nodes in the path.
Detailed Dry Run
Insert('apple', 3): Nodes a,p,p,l,e each get +3 sum. Insert('app', 2): Nodes a,p,p get +2. Sum('ap') -> 5.
Replace Words
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 successor. For example, when the root "an" is followed by the successor word "other", we can form a new word "another".\n\nGiven a dictionary consisting of many roots and a sentence consisting of words separated by spaces, replace all the successors in the sentence with the root forming it. If a successor can be replaced by more than one root, replace it with the root that has the shortest length.
Examples
Level I: HashSet of Roots
Intuition
Store all roots in a HashSet. For each word in the sentence, check every possible prefix (starting from length 1) in the HashSet. Return the first match found.
Detailed Dry Run
Word: 'cattle'. Check 'c', 'ca', 'cat'. 'cat' is in HashSet. Replace 'cattle' with 'cat'.
Level III: Trie (Optimal)
Intuition
Build a Trie from the roots. For each word in the sentence, traverse the Trie. The first isEndOfWord node encountered provides the shortest root.
Detailed Dry Run
Trie: root -> c -> a -> t (end). Word: 'cattle'. Traverse c -> a -> t (found end). Return 'cat'.
Word Search II
The Challenge of Multiple Words
If we have words, running a standard Word Search (DFS) for each word takes . This is inefficient because many words share prefixes (e.g., 'apple' and 'apply').
Trie for Prefix Sharing
By storing the dictionary in a Trie, we can search for all words simultaneously. As we traverse the grid in DFS, we also traverse the Trie. If a path in the grid doesn't match any prefix in the Trie, we prune the search immediately.
Optimization: Trie Pruning
Once a word is found, we can remove it from the Trie (or mark it as found) and potentially delete leaf nodes that are no longer part of any other words. This drastically reduces the search space for subsequent cells.
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. Includes visual Trie-based pruning trace. The same letter cell may not be used more than once in a word.
Examples
Level I: Naive DFS (Multiple Word searches)
Intuition
Iterate through each word in the list and perform a standard DFS search on the board.
For each word in words, scan every cell (r, c). If board[r][c] == word[0], start a DFS to find the rest of the characters.
Detailed Dry Run
Searching 'oath'. Find 'o' at (0,0). Neighbors: (0,1) 'a'. Next: (1,1) 't'. Next (0,1) NO, (0,2) NO, (1,0) NO... SUCCESS.
Level II: Backtracking with Trie
Intuition
Insert all words into a Trie. Traverse the board and the Trie simultaneously to find matches.
Starting at each cell, if the character exists as a child of the Trie root, move into the Trie. If node.isEndOfWord, add the word to result.
Detailed Dry Run
Trie: root -> o -> a -> t -> h (END). At (0,0) 'o': matches root.child('o'). Neighbors of (0,0): 'a' at (0,1) matches 'o'.child('a'). Neighbors of (0,1): 't' at (1,1) matches 'a'.child('t')...
Level III: Optimized Trie (Node Removal)
Intuition
When a leaf node is found and its word is extracted, if it has no children, we can delete the node from its parent to avoid re-visiting dead branches.
Add a parent pointer or count to each node. When word != "", set it to null. If children count becomes 0, remove the entry from search path completely.
Detailed Dry Run
Trie: root -> o -> a -> t -> h (END). After finding 'oath', the path 'h' -> 't' -> 'a' -> 'o' is pruned if no other words use them. Subsequent board scans will skip 'o' immediately.
Design Search Autocomplete System
Design a search autocomplete system for a search engine. Users may input a sentence (at least one character and end with a special character '#'). For each character they type except '#', you need to return the top 3 historical hot sentences that have a prefix the same as the part of the sentence already typed.
Visual Representation
Typed: "i"
Prefix "i" matches "i love you" (5), "island" (3), "ironman" (2).
Return: ["i love you", "island", "ironman"]
Typed: " " (space)
Prefix "i " matches "i love you" (5), "i a cool" (2).
Return: ["i love you", "i a cool"]Examples
Level I: HashMap (Brute Force Prefix Search)
Intuition
Maintain a HashMap of historical sentences and their counts. For every input, iterate through the entire map, collect sentences starting with the prefix, and sort them to find the top 3. Simple but O(N * L) per query.
Level III: Trie + Top-3 Cache
Intuition
Store each historical sentence in a Trie. Each node stores a list of the top 3 hot sentences passing through it. Update these lists incrementally to ensure O(1) query time (just return the list at the current node).
Word Squares
Given an array of unique strings words, return all the word squares you can build from words. The same word from words can be used multiple times. A sequence of words forms a valid word square if the kth row and kth column read the exact same string for each 0 <= k < max(numRows, numColumns).
Examples
Level I: Brute Force Backtracking
Intuition
Try all possible word combinations recursively. At each step, check if the current partial square is valid. Very slow as it doesn't use the prefix property effectively.
Level III: Trie + Backtracking
Intuition
At each step k, we need to find words that start with a specific prefix. This prefix is formed by the characters at position k of the words already chosen. A Trie is the perfect data structure to quickly fetch all words sharing a common prefix.
Maximum XOR of Two Numbers in an Array
Given an integer array nums, return the maximum result of nums[i] XOR nums[j], where 0 <= i <= j < n.
Visual Representation
Nums: [3, 10, 5, 25, 2, 8]
Max XOR: 5 XOR 25 = 28
Binary Trie approach:
1. Insert all numbers as 32-bit binary strings into a Trie.
2. For each number X, find a path that maximizes XOR.
3. To maximize XOR, at each bit, try to go to the opposite bit (if curr is 0, try 1).
If opposite exists, result |= (1 << bit).Examples
Level I: Brute Force
Intuition
Check every pair of numbers in the array and calculate their XOR. Keep track of the maximum XOR found. Simple but .
Level III: Binary Trie
Intuition
Represent each number as a 31-bit or 32-bit binary string. Insert all numbers into a Trie where each node has at most two children (0 and 1). For each number , we want to find a number such that is maximized. At each bit of , if the -th bit is , we prioritize moving to a child representing in the Trie. This greedy choice at each bit ensures the maximum possible XOR value.
Camelcase Matching
A query word matches a given pattern if we can insert lowercase letters to the pattern so that it equals the query. Return a list of booleans.
Visual Representation
Queries: ["FooBar", "FooBarTest", "FootBall"]
Pattern: "FB"
"FooBar" -> F-oo-B-ar -> Matches!
"FooBarTest" -> F-oo-B-ar-T-est -> Extra 'T' (Uppercase) -> Fails!Examples
Level I: Two Pointers (Standard Search)
Intuition
For each word, use two pointers to check if the pattern is a subsequence. Additionally, ensure that any uppercase letters in the word that are NOT in the pattern cause a mismatch.
Level III: Two Pointers / Trie
Intuition
For each query, use two pointers ( for query, for pattern). If , increment both. If is lowercase, increment only . If is uppercase and doesn't match , it's a mismatch. After the loop, if reached pattern length and no extra uppercase letters remain in query, it's a match.
Prefix and Suffix Search
Design a data structure that allows searching for a word with a given prefix AND a given suffix. If there are multiple valid words, return the largest index.
Visual Representation
Words: ["apple"]
Suffix#Prefix construction: "e#apple", "le#apple", "ple#apple", ...
Insert these into a Trie.
Query: prefix="ap", suffix="le"
Search Trie for "le#ap".
Found node stores max index: 0.Examples
Level I: HashMap (All Combinations)
Intuition
Pre-calculate all potential prefix + '#' + suffix combinations for every word and store them in a HashMap with their index. Query is O(1). Inefficient space but simple.
Level III: Wrapped Trie ({suffix}#{prefix})
Intuition
To handle both prefix and suffix in a single search, we can insert multiple variations of each word into the Trie. For a word like 'apple', insert: '#apple', 'e#apple', 'le#apple', 'ple#apple', 'pple#apple', 'apple#apple'. A query with prefix 'a' and suffix 'le' becomes a prefix search for 'le#a' in this special Trie.
Word Ladder II
Given two words, beginWord and endWord, and a dictionary wordList, return all shortest transformation sequences from beginWord to endWord. Only one letter can be changed at a time.
Examples
Level I: Standard BFS (Shortest Transformation)
Intuition
Use a standard BFS to find the shortest distance from beginWord to endWord. In each step, change one character at a time and check if it exists in the dictionary. This approach only finds the length of the shortest path, not the actual paths themselves.
Level III: BFS + Backtracking (Trie for Neighbors)
Intuition
Use BFS to find the shortest distance from beginWord to all reachable words. To quickly find 'neighbors' (words differing by one char), we can iterate through each position of the word and try all 26 letters, checking against a Set/Trie. Finally, use DFS to backtrack all shortest paths from beginWord to endWord using the distance levels found in BFS.
Concatenated Words
Given an array of strings words (without duplicates), return all the concatenated words in the given list of words. A concatenated word is defined as a string that is entirely comprised of at least two shorter words in the given array.
Visual Representation
Words: ["cat", "dog", "catdog"]
DFS(0):
1. Prefixes found: "cat" (Trie matches).
2. Remaining: "dog".
3. DFS("dog"): Prefix found: "dog". Remaining: "".
4. SUCCESS -> "catdog" is concatenated.Examples
Level I: Set-based DFS
Intuition
Use a HashSet to store all words. For each word, check if it can be partitioned into at least two shorter words present in the Set using a recursive DFS with memoization. Simple but less space-efficient for prefix matching.
Level III: Trie + DFS + Memoization
Intuition
First, sort the words by length. This allows us to only search for a word's composition using words that were previously added to the Trie (which are guaranteed to be shorter). For each word, use DFS with a Trie to check if it can be broken into multiple pieces that are already in the Trie.
Palindrome Pairs
Given a list of unique words, return all pairs of distinct indices (i, j) such that the concatenation of the two words words[i] + words[j] is a palindrome.
Visual Representation
Words: ["abcd", "dcba", "lls", "s", "sssll"]
Pairs:
1. "abcd" + "dcba" = "abcddcba" (Palindrome)
2. "dcba" + "abcd" = "dcbaabcd" (Palindrome)
3. "s" + "lls" = "slls" (Palindrome)
4. "sssll" + "lls" = "ssslllls" (Wait, no...)
Wait: "lls" + "s" = "llss" (No)
Actual: "sssll" + "lls" -> "ssslllls" NO.
Let's check "lls" reverse is "sll".
If we pick i="sssll", j="lls", words[i]+words[j] = "ssslllls" Fails.
Correct for "lls": s + lls = slls (Correct).Examples
Level I: Brute Force (Check all pairs)
Intuition
Iterate through every pair of indices in the array. Construct the string words[i] + words[j] and check if it is a palindrome. This is , which is too slow for large inputs.
Level III: Trie (Suffix-to-Prefix matching)
Intuition
For a word , we want to find such that is a palindrome. This happens if can be split into where is a palindrome and is the reverse of , OR if where is a palindrome and is the reverse of . A Trie storing words in reverse allows us to efficiently find these matches.
Shortest Unique Prefix
Given an array of words, find the shortest unique prefix of each word, such that the prefix is not a prefix of any other word.
Visual Representation
Words: ["zebra", "dog", "duck", "dove"]
Trie with counts:
root
-> z(1) -> e(1) ... -> found count=1 at 'z'
-> d(3)
-> o(2)
-> g(1) -> found count=1 at 'g' (prefix "dog")
-> v(1) -> found count=1 at 'v' (prefix "dov")
-> u(1) -> found count=1 at 'u' (prefix "du")Examples
Level I: Brute Force Comparison
Intuition
For each word, check every possible prefix starting from length 1. Compare this prefix against all other words. The first prefix that is not a prefix of any other word is the shortest unique prefix for that word.
Level III: Trie (Node Visited Count)
Intuition
Insert all words into a Trie. As you insert, increment a visited count (count) for each node along the path. To find the shortest unique prefix for a word, traverse the Trie until you reach a node with count == 1. That node marks the end of the shortest unique prefix.
Maximum XOR with an Element from Array
Given an integer array nums, return the maximum value of nums[j] XOR xi where nums[j] <= mi. If all numbers in nums are larger than mi, the answer is -1.
Visual Representation
Nums: [0, 1, 2, 3, 4]
Query: [3, 1] (x=3, m=1)
Valid nums <= 1: [0, 1]
XOR results: 3^0=3, 3^1=2
Max: 3
Optimal approach:
1. Sort nums.
2. Sort queries by m.
3. Insert nums into Trie as long as nums[j] <= m.
4. Query Trie for max XOR.Examples
Level I: Brute Force
Intuition
For each query [xi, mi], iterate through every number in the nums array. If nums[j] <= mi, calculate nums[j] XOR xi and update the maximum. Simple O(N*Q) solution.
Level III: Offline Queries + Binary Trie
Intuition
Since we need to check , we can sort both and (by ). By processing queries offline in non-decreasing order of , we only insert numbers into the Trie when they become 'valid' for the current . This transforms the conditional maximum problem into a standard Maximum XOR problem on a growing Trie.
Word Break II
Given a string s and a string dictionary wordDict, return all possible sentences that can be formed by adding spaces to s such that each word is a valid dictionary word.
Visual Representation
s = "catsanddog", dict = ["cat","cats","and","sand","dog"]
Trie path 1: root -> c -> a -> t -> s (word) -> a -> n -> d (word) -> d -> o -> g (word)
Trie path 2: root -> c -> a -> t (word) -> s -> a -> n -> d (word) -> d -> o -> g (word)
Sentences:
1. "cats and dog"
2. "cat sand dog"Examples
Level I: Standard Recursion
Intuition
Iterate through the string s. If a prefix exists in the dictionary, recursively solve for the remaining suffix. O(2^N).
Level III: Trie + DFS + Memoization
Intuition
Use a Trie to store words for fast prefix lookups. Combine DFS with memoization to store results for each starting index.
Boggle Game
Find the maximum number of words you can find in a Boggle board such that no two words overlap (i.e., they don't share any cell).
Visual Representation
Board: ["abc", "def", "ghi"]
Words: ["abc", "defi", "gh"]
Possible Selections:
1. {"abc", "def"} -> 2 words
2. {"abc"} -> 1 word
Result: 2Examples
Level I: Brute Force DFS
Intuition
For each word in the dictionary, perform a standard DFS on the board to see if it exists. Keep track of used cells and try all combinations of non-overlapping words. Very slow but simple.
Level III: Trie + Backtracking (Max Independent Sets)
Intuition
This is a variant of Word Search II. First, find all occurrences of all dictionary words on the board (Word Search II style). Then, treat this as a problem of finding the maximum number of disjoint sets (words) using backtracking. Each word is a set of cell coordinates.
Stream of Characters
Design a data structure that accepts a stream of characters and checks if any suffix of the characters queried so far is in a given list of words.
Examples
Level I: Brute Force Suffix Comparison
Intuition
Store all arrived characters in a list. For every query, iterate through all words in the dictionary and check if any word matches the current suffix of the character stream. O(N * L) per query.
Level III: Reverse Trie
Intuition
Since we need to match a suffix of the stream, it's efficient to store the dictionary words in a Trie in reversed order. As new characters arrive in the stream, we store them in a buffer (or current path). To check if any suffix matches, we traverse the reversed Trie starting from the last character received.
Longest Word in Dictionary through Deleting
Given a string s and a string dictionary dictionary, return the longest string in the dictionary that can be formed by deleting some characters of the given string s.
Examples
Level I: Two Pointers (Standard Search)
Intuition
Iterate through each word in the dictionary. Use two pointers to check if the word is a subsequence of s. Keep track of the longest word found (using lexicographical order for ties).
Level III: Trie (Index-based Subsequence Search)
Intuition
Store s in a way that allows fast subsequence checks. For each character a-z, store a list of indices where it appears in s. For any word, use binary search to find the smallest valid index for the next character. A Trie of words can also be used to explore multiple words simultaneously.
Prefix and Suffix Search (Optimized Query)
The same as Prefix and Suffix Search, but focused on extreme query performance.
Level I: Brute Force Search
Intuition
For each query, iterate through the entire dictionary. For each word, check if it starts with the prefix and ends with the suffix. Return the maximum index found. O(N * L) per query.
Level III: Precomputed HashMap of Pairs
Intuition
For cases where memory is available but query speed is critical, precompute all possible (prefix, suffix) pairs for every word. Use a HashMap to store prefix + " " + suffix as the key and the max index as the value.
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.