Search Suggestions System
For each prefix of searchWord, return up to 3 lexicographically smallest matching products.
4 min read
trie sorting binary-search
Problem#
Given a list of products and a searchWord, for each prefix searchWord[0..i] return up to 3 lexicographically smallest products that start with that prefix.
Examples#
products = ["mobile","mouse","moneypot","monitor","mousepad"], searchWord = "mouse"→[["mobile","moneypot","monitor"],["mobile","moneypot","monitor"],["mouse","mousepad"],["mouse","mousepad"],["mouse","mousepad"]].
Constraints#
1 <= products.length <= 1000,1 <= products[i].length, searchWord.length <= 1000.
Hints#
Hint
Sort products. For each prefix, the matching range is contiguous — find it with binary search.
Approach#
Trie — store all products; at each prefix walk the trie and DFS-collect first 3 words.
Sort + binary search — sort once; for each prefix,
lower_bound on the prefix and read at most 3 strings. After sorting, all products matching a prefix occupy a contiguous range. Use lower_bound to find the start; take up to 3 while the prefix matches.
Solution#
class Solution {public: vector<vector<string>> suggestedProducts(vector<string>& products, string searchWord) { sort(products.begin(), products.end()); vector<vector<string>> ans; ans.reserve(searchWord.size()); string prefix; for (char c : searchWord) { prefix.push_back(c); auto it = lower_bound(products.begin(), products.end(), prefix); vector<string> row; for (int k = 0; k < 3 && it + k != products.end(); ++k) { const string& p = *(it + k); if (p.compare(0, prefix.size(), prefix) != 0) break; row.push_back(p); } ans.push_back(std::move(row)); } return ans; }};import ( "sort" "strings")
func suggestedProducts(products []string, searchWord string) [][]string { sort.Strings(products) ans := make([][]string, 0, len(searchWord)) prefix := "" for _, c := range searchWord { prefix += string(c) it := sort.SearchStrings(products, prefix) row := []string{} for k := 0; k < 3 && it+k < len(products); k++ { p := products[it+k] if !strings.HasPrefix(p, prefix) { break } row = append(row, p) } ans = append(ans, row) } return ans}from bisect import bisect_leftfrom typing import List
class Solution: def suggestedProducts(self, products: List[str], searchWord: str) -> List[List[str]]: products.sort() ans: List[List[str]] = [] prefix = "" for c in searchWord: prefix += c i = bisect_left(products, prefix) row: List[str] = [] for k in range(3): if i + k >= len(products): break p = products[i + k] if not p.startswith(prefix): break row.append(p) ans.append(row) return ansfunction suggestedProducts(products, searchWord) { products.sort(); const ans = []; let prefix = ""; for (const c of searchWord) { prefix += c; let lo = 0, hi = products.length; while (lo < hi) { const mid = (lo + hi) >> 1; if (products[mid] < prefix) lo = mid + 1; else hi = mid; } const row = []; for (let k = 0; k < 3 && lo + k < products.length; k++) { const p = products[lo + k]; if (!p.startsWith(prefix)) break; row.push(p); } ans.push(row); } return ans;}class Solution { public List<List<String>> suggestedProducts(String[] products, String searchWord) { Arrays.sort(products); List<List<String>> ans = new ArrayList<>(); StringBuilder prefix = new StringBuilder(); for (char c : searchWord.toCharArray()) { prefix.append(c); String p = prefix.toString(); int lo = 0, hi = products.length; while (lo < hi) { int mid = (lo + hi) >>> 1; if (products[mid].compareTo(p) < 0) lo = mid + 1; else hi = mid; } List<String> row = new ArrayList<>(); for (int k = 0; k < 3 && lo + k < products.length; k++) { String s = products[lo + k]; if (!s.startsWith(p)) break; row.add(s); } ans.add(row); } return ans; }}function suggestedProducts(products: string[], searchWord: string): string[][] { products.sort(); const ans: string[][] = []; let prefix = ""; for (const c of searchWord) { prefix += c; let lo = 0, hi = products.length; while (lo < hi) { const mid = (lo + hi) >> 1; if (products[mid] < prefix) lo = mid + 1; else hi = mid; } const row: string[] = []; for (let k = 0; k < 3 && lo + k < products.length; k++) { const p = products[lo + k]; if (!p.startsWith(prefix)) break; row.push(p); } ans.push(row); } return ans;}Editorial#
Sorting once is O(N log N). Each prefix lookup is O(log N + L) (binary search plus prefix comparison cost). Trie-based solutions are equally valid but with worse constant factors and more memory; the sort approach is the cleanest answer in C++ since std::lower_bound plus compare is just two function calls.
Complexity#
- Time: O(N log N + sum of |prefix| * 3).
- Space: O(1) extra beyond the output.
Concept revision#
Related#