Design Search Autocomplete System
Design a search autocomplete system for a search engine. Users may input a sentence (at least one word and end with a special character '#'
). For each character they type except '#', you need to return the top 3historical hot sentences that have prefix the same as the part of sentence already typed. Here are the specific rules:
- The hot degree for a sentence is defined as the number of times a user typed the exactly same sentence before.
- The returned top 3 hot sentences should be sorted by hot degree (The first is the hottest one). If several sentences have the same degree of hot, you need to use ASCII-code order (smaller one appears first).
- If less than 3 hot sentences exist, then just return as many as you can.
- When the input is a special character, it means the sentence ends, and in this case, you need to return an empty list.
Your job is to implement the following functions:
The constructor function:
AutocompleteSystem(String[] sentences, int[] times):
This is the constructor. The input is historical data. Sentences
is a string array consists of previously typed sentences. Times
is the corresponding times a sentence has been typed. Your system should record these historical data.
Now, the user wants to input a new sentence. The following function will provide the next character the user types:
List<String> input(char c):
The input c
is the next character typed by the user. The character will only be lower-case letters ('a'
to 'z'
), blank space (' '
) or a special character ('#'
). Also, the previously typed sentence should be recorded in your system. The output will be the top 3 historical hot sentences that have prefix the same as the part of sentence already typed.
Example:
Operation: AutocompleteSystem(["i love you", "island","ironman", "i love leetcode"], [5,3,2,2])
The system have already tracked down the following sentences and their corresponding times: "i love you"
: 5
times "island"
: 3
times "ironman"
: 2
times "i love leetcode"
: 2
times
Now, the user begins another search:
Operation: input('i')
Output: ["i love you", "island","i love leetcode"]
Explanation:
There are four sentences that have prefix "i"
. Among them, "ironman" and "i love leetcode" have same hot degree. Since ' '
has ASCII code 32 and 'r'
has ASCII code 114, "i love leetcode" should be in front of "ironman". Also we only need to output top 3 hot sentences, so "ironman" will be ignored.
Operation: input(' ')
Output: ["i love you","i love leetcode"]
Explanation:
There are only two sentences that have prefix "i "
.
Operation: input('a')
Output: []
Explanation:
There are no sentences that have prefix "i a"
.
Operation: input('#')
Output: []
Explanation:
The user finished the input, the sentence "i a"
should be saved as a historical sentence in system. And the following input will be counted as a new search.
1 class AutocompleteSystem { 2 private final Map<String, Integer> cache = new HashMap<String, Integer>(); 3 private String input = ""; 4 5 public AutocompleteSystem(String[] sentences, int[] times) { 6 for (int i = 0; i < sentences.length; i++) { 7 cache.put(sentences[i], times[i]); 8 } 9 } 10 11 public List<String> input(char c) { 12 if (c == '#') { 13 Integer count = cache.getOrDefault(input, 0); 14 cache.put(input, ++count); 15 input = ""; 16 return Collections.emptyList(); 17 } 18 19 input += c; 20 return cache.entrySet().stream() 21 .filter(e -> e.getKey().startsWith(input)) 22 .sorted(Map.Entry.<String, Integer>comparingByValue(Comparator.reverseOrder()) 23 .thenComparing(Map.Entry.comparingByKey())) 24 .limit(3) 25 .map(Map.Entry::getKey) 26 .collect(Collectors.toCollection(ArrayList::new)); 27 } 28 }
1 class AutocompleteSystem { 2 class TrieNode { 3 Map<Character, TrieNode> children; 4 Map<String, Integer> counts; 5 boolean isWord; 6 7 public TrieNode() { 8 children = new HashMap<>(); 9 counts = new HashMap<>(); 10 isWord = false; 11 } 12 } 13 14 TrieNode root; 15 String prefix; 16 17 public AutocompleteSystem(String[] sentences, int[] times) { 18 root = new TrieNode(); 19 prefix = ""; 20 for (int i = 0; i < sentences.length; i++) { 21 add(sentences[i], times[i]); 22 } 23 } 24 25 private void add(String s, int count) { 26 TrieNode cur = root; 27 for (char c : s.toCharArray()) { 28 TrieNode next = cur.children.get(c); 29 if (next == null) { 30 next = new TrieNode(); 31 cur.children.put(c, next); 32 } 33 cur = next; 34 cur.counts.put(s, cur.counts.getOrDefault(s, 0) + count); 35 } 36 cur.isWord = true; 37 } 38 39 public List<String> input(char c) { 40 if (c == '#') { 41 add(prefix, 1); 42 prefix = ""; 43 return new ArrayList<String>(); 44 } 45 46 prefix = prefix + c; 47 TrieNode cur = root; 48 for (char ch : prefix.toCharArray()) { 49 TrieNode next = cur.children.get(ch); 50 if (next == null) { 51 return new ArrayList<String>(); 52 } 53 cur = next; 54 } 55 56 PriorityQueue<Map.Entry<String, Integer>> pq = new PriorityQueue<>((a, 57 b) -> (a.getValue() == b.getValue() ? a.getKey().compareTo(b.getKey()) : b.getValue() - a.getValue())); 58 for (Map.Entry<String, Integer> entry : cur.counts.entrySet()) { 59 pq.add(entry); 60 } 61 62 List<String> res = new ArrayList<>(); 63 for (int i = 0; i < 3 && !pq.isEmpty(); i++) { 64 res.add(pq.poll().getKey()); 65 } 66 return res; 67 } 68 }