[LeetCode] 642. 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 3 historical 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:
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
Operation: input(' ')
Output: ["i love you","i love leetcode"]
Explanation:
There are only two sentences that have prefix
Operation: input('a')
Output: []
Explanation:
There are no sentences that have prefix
Operation: input('#')
Output: []
Explanation:
The user finished the input, the sentence
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
timesNow, 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.
Note:
- The input sentence will always start with a letter and end with '#', and only one blank space will exist between two words.
- The number of complete sentences that to be searched won't exceed 100. The length of each sentence including those in the historical data won't exceed 100.
- Please use double-quote instead of single-quote when you write test cases even for a character input.
- Please remember to RESET your class variables declared in class AutocompleteSystem, as static/class variables are persisted across multiple test cases. Please see here for more details.
Thought process:
Use trie. Instead of using a boolean field to indicate if this node is a word, use an int to store the times the word appeared.
- Constructor: insert sentences and times into trie.
- Input:
- If char == '#', add stored string to trie and return an empty list.
- Otherwise, add the char to stored string and traverse trie to get auto-complete result.
Solution:
Time complexity:
Say n is the number of sentences in the dictionary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | class AutocompleteSystem { class Entry { String sentence; int times; Entry(String sentence, int times) { this.sentence = sentence; this.times = times; } } class TrieNode { TrieNode[] children; int times; TrieNode() { children = new TrieNode[27]; times = 0; } } private TrieNode root; private TrieNode previous; private String query; public AutocompleteSystem(String[] sentences, int[] times) { root = new TrieNode(); query = ""; for (int i = 0; i < sentences.length; i++) { insert(sentences[i], times[i]); } } public List<String> input(char c) { List<String> result = new ArrayList<>(); if (c == '#') { insert(query, 1); previous = null; query = ""; return result; } query += c; List<Entry> history = lookup(c); history.sort((a, b) -> { if (a.times == b.times) { return a.sentence.compareTo(b.sentence); } return b.times - a.times; }); for (int i = 0; i < Math.min(history.size(), 3); i++) { result.add(history.get(i).sentence); } return result; } private void insert(String sentence, int times) { TrieNode current = root; for (char c : sentence.toCharArray()) { int index = c == ' ' ? 26 : c - 'a'; if (current.children[index] == null) { current.children[index] = new TrieNode(); } current = current.children[index]; } current.times += times; } private List<Entry> lookup(char c) { List<Entry> history = new ArrayList<>(); if (previous == null && query.length() > 1) { return history; } TrieNode current = previous == null ? root : previous; int index = c == ' ' ? 26 : c - 'a'; if (current.children[index] == null) { previous = null; return history; } previous = current.children[index]; traverse(query, previous, history); return history; } private void traverse(String s, TrieNode node, List<Entry> history) { if (node.times > 0) { history.add(new Entry(s, node.times)); } for (int i = 0; i < 27; i++) { if (node.children[i] != null) { String next = i == 26 ? s + ' ' : s + (char) ('a' + i); traverse(next, node.children[i], history); } } } } /** * Your AutocompleteSystem object will be instantiated and called as such: * AutocompleteSystem obj = new AutocompleteSystem(sentences, times); * List<String> param_1 = obj.input(c); */ |
Time complexity:
Say n is the number of sentences in the dictionary.
- Constructor: O(n).
- input(): say the depth of the trie is d, and every trie node has an average of c children. lookup and traverse together take O(c^d). Sorting the history takes O(nlogn). So the overall time complexity is O(c^d + nlogn).
Comments
Post a Comment