Search Suggestions System
Problem
Given an array of strings products and a string searchWord, return a list of lists of strings representing the suggested products after each character of searchWord is typed, where each suggestion list contains at most three lexicographically minimum products starting with the current prefix.
- 1 ≤ products.length ≤ 1000
- 1 ≤ products[i].length ≤ 3000
- 1 ≤ searchWord.length ≤ 1000
- All strings consist of lowercase English letters.
- All products are unique.
Example
products = ["mobile","mouse","moneypot","monitor","mousepad"], searchWord = "mouse"[["mobile","moneypot","monitor"],["mobile","moneypot","monitor"],["mouse","mousepad"],["mouse","mousepad"],["mouse","mousepad"]]First, products are sorted lexicographically: ["mobile","moneypot","monitor","mouse","mousepad"]. For each prefix of "mouse": - Prefix "m": suggestions are the first three products starting with 'm': ["mobile","moneypot","monitor"]. - Prefix "mo": same as above. - Prefix "mou": products starting with 'mou' are ["mouse","mousepad"]. - Prefix "mous": same as above. - Prefix "mouse": same as above. The algorithm builds a trie to efficiently track these suggestions for each prefix, enabling quick retrieval of up to three lexicographically smallest matches.
Approach
Straightforward Solution
A brute-force approach would, for each prefix of searchWord, scan all products to find matches and then sort them to pick the top three. This approach is O(m * n * k log k) where m is length of searchWord, n is number of products, and k is average product length, which is inefficient for large inputs.
Core Observation
The problem requires efficient prefix-based retrieval of lexicographically smallest suggestions. A trie is a natural data structure for prefix queries, allowing incremental traversal and storage of suggestions at each node.
Path to Optimal
PreviewSorting products lexicographically upfront allows building a trie where each node maintains up to three suggestions in sorted order. Inserting products in sorted order ensures suggestions are always the lexicographically smallest…
Full step-by-step walkthrough on Pro →
Optimal Approach
PreviewSort products lexicographically. Build a trie where each node stores up to three suggestions…
Full step-by-step walkthrough on Pro →
Want the full reasoning chain?
Unlock the complete walkthrough, line-by-line analysis, and recall drill.
Unlock ProTime
O(n * k + m)
Sorting products takes O(n log n). Building the trie takes O(n * k), where n is number of products and k is average length. Each insertion updates up to k nodes, each with O(1) append since suggestions capped at 3. Querying for m prefixes takes O(m). Overall dominated by O(n * k).
Space
O(n * k)
The trie stores nodes for all characters of all products, with each node holding up to three suggestions. This results in O(n * k) auxiliary space, excluding the input and output.
Pattern Spotlight
Trie (Prefix Tree with Stored Suggestions)
When needing to retrieve lexicographically smallest suggestions for multiple prefixes, pre-sort inputs and store limited-size suggestion lists at each trie node during insertion to enable O(1) retrieval per prefix without repeated sorting.
Solution
| 1 | class TrieNode: |
| 2 | def __init__(self): |
| 3 | self.children = {} |
| 4 | self.suggestions = [] |
| 5 | |
| 6 | class Solution: |
| 7 | def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]: |
| 8 | products.sort() |
| 9 | root = TrieNode() |
| 10 | |
| 11 | for word in products: |
| 12 | node = root |
| 13 | for c in word: |
| 14 | if c not in node.children: |
| 15 | node.children[c] = TrieNode() |
| 16 | node = node.children[c] |
| 17 | |
| 18 | if len(node.suggestions) < 3: |
| 19 | node.suggestions.append(word) |
| 20 | |
| 21 | res = [] |
| 22 | node = root |
| 23 | |
| 24 | for c in searchWord: |
| 25 | if node and c in node.children: |
| 26 | node = node.children[c] |
| 27 | res.append(node.suggestions) |
| 28 | else: |
| 29 | node = None |
| 30 | res.append([]) |
| 31 | |
| 32 | return res |
Step-by-Step Solution
Sort Products and Build Trie with Limited Suggestions per Node
| 8 | products.sort() |
| 9 | root = TrieNode() |
| 11 | for word in products: |
| 12 | node = root |
| 13 | for c in word: |
| 14 | if c not in node.children: |
| 15 | node.children[c] = TrieNode() |
| 16 | node = node.children[c] |
| 18 | if len(node.suggestions) < 3: |
| 19 | node.suggestions.append(word) |
Objective
To prepare the data structure by sorting products and constructing a trie that stores up to three lexicographically smallest suggestions at each node.
Key Insight
Sorting products upfront ensures that when inserting them into the trie, the first products encountered at each node are the lexicographically smallest. By appending products to the suggestions list only if it has fewer than three items, the trie nodes maintain a capped list of top suggestions without needing to sort repeatedly. This precomputation enables fast prefix queries later.
Interview Quick-Check
Core Logic
Sorting products before insertion guarantees that suggestions stored at each trie node are lexicographically ordered and limited to three, enabling O(1) retrieval.
State & Boundaries
Each trie node stores a dictionary of children and a list of up to three suggestions, ensuring bounded memory per node.
Common Pitfalls & Bugs
Failing to limit suggestions to three per node can cause excessive memory usage and degrade performance.
Complexity
Building the trie takes O(n * k) time, where n is number of products and k is average product length.
Traverse Trie to Collect Suggestions for Each Prefix of SearchWord
To efficiently retrieve up to three suggestions for each prefix of searchWord by traversing the trie nodes corresponding to each character.
Return the Aggregated List of Suggestions for All Prefixes
To output the final list of suggestion lists corresponding to each prefix of searchWord.
2 more steps with full analysis available on Pro.
Line Analysis
This solution has 5 Critical lines interviewers watch for.
self.children = {}
Initialize a dictionary that maps characters to child TrieNode objects.
Each trie node must maintain references to its next characters so prefix traversal can proceed efficiently. Using a dictionary keyed by character enables constant-time lookup when inserting products and when traversing prefixes during queries.
self.suggestions = []
Initialize a list to store up to three product suggestions for this prefix.
Each node caches up to three lexicographically smallest products that share the prefix represented by this node. Storing these suggestions during trie construction allows prefix queries to return results instantly without searching or sorting at query time.
products.sort()
Sort the products array lexicographically.
Sorting ensures that when products are inserted into the trie, suggestions stored at each node are the lexicographically smallest, enabling efficient retrieval without further sorting.
Full line-by-line criticality + rationale for all 22 lines available on Pro.
Test Your Understanding
Why does storing up to three suggestions at each trie node during insertion guarantee lexicographically smallest suggestions for any prefix?
See the answer with Pro.
Related Problems
Trie pattern
Don't just read it. Drill it.
Reconstruct Search Suggestions System from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Search Suggestions System drill