Trie · Medium
LeetCode 1268: Search Suggestions System — solution
Sort the products once, then for each typed prefix binary-search the first match and take up to three neighbours. O(n log n * L) for sorting plus O(m * (log n + L)) per query loop, with O(1) extra space besides the output.
Problem in plain words
You have a list of product names and a word that the user types one letter at a time. After each letter, suggest up to three products that start with what has been typed so far, choosing the alphabetically smallest ones. Return the list of suggestions for every step.
For example, with products ["pen", "pencil", "pear"] and the word "pe", both steps suggest ["pear", "pen", "pencil"].
Intuition
After sorting, every product that starts with a given prefix sits in one contiguous block, and the block starts at the first position where the prefix would be inserted. So for each prefix, binary-search that position and look at the next three products; any of them that still start with the prefix are the answer. A trie also solves this, but sorting plus binary search is shorter to write and uses far less memory.
Approach
- Sort
productslexicographically. - For each length
ifrom 1 tosearchWord.length, letprefixbe the firstiletters. - Find
start, the first index whose product is greater than or equal toprefix(a lower bound). Because prefixes only grow, the search can begin from the previousstart. - Collect products at
start,start + 1,start + 2while they exist and start withprefix. - Append that list (possibly empty) to the result.
Dry run
Input: products ["bean", "bagel", "banana", "bolt", "bake"], word "bak". Sorted: bagel, bake, banana, bean, bolt.
| Prefix | Lower bound | Next three that match |
|---|---|---|
| b | 0 (bagel) | bagel, bake, banana |
| ba | 0 (bagel) | bagel, bake, banana |
| bak | 1 (bake) | bake (banana does not start with bak) |
Java solution
class Solution {
public List<List<String>> suggestedProducts(String[] products, String searchWord) {
Arrays.sort(products);
List<List<String>> result = new ArrayList<>();
int start = 0;
for (int i = 1; i <= searchWord.length(); i++) {
String prefix = searchWord.substring(0, i);
start = lowerBound(products, start, prefix);
List<String> suggestions = new ArrayList<>();
for (int j = start; j < products.length && j < start + 3; j++) {
if (!products[j].startsWith(prefix)) break;
suggestions.add(products[j]);
}
result.add(suggestions);
}
return result;
}
// First index in [from, n) whose value is >= target.
private int lowerBound(String[] arr, int from, String target) {
int lo = from, hi = arr.length;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (arr[mid].compareTo(target) < 0) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
}Python solution
import bisect
class Solution:
def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]:
products.sort()
result = []
start = 0
prefix = ""
for ch in searchWord:
prefix += ch
start = bisect.bisect_left(products, prefix, start)
suggestions = []
for word in products[start:start + 3]:
if not word.startswith(prefix):
break
suggestions.append(word)
result.append(suggestions)
return resultComplexity
| Measure | Cost | Why |
|---|---|---|
| Time | O(n log n * L + m * L * log n) | Sorting compares strings of length up to L; each of the m prefixes then does a binary search with string comparisons. |
| Space | O(1) extra | Sorting is in place apart from the language's sort buffer; the output holds at most 3m strings. |
Edge cases and common mistakes
- No match: once a prefix matches nothing, every later step must still add an empty list, so the result always has
searchWord.lengthentries. - Reusing `start`: it is safe because a longer prefix can never sort before a shorter one it extends.
- Taking three neighbours without checking: products after
startmay not share the prefix; always verify withstartsWith. - Duplicates in the input: identical product names are allowed and both appear in the suggestions.
›When is the trie version better?
If the product list is fixed and you serve many different search words, a trie that stores the top three words at each node answers every keystroke in O(1) after an up-front build.
