LeetCode上第 642 号问题:Design Search Autocomplete Systemnode
为搜索引擎设计一个搜索自动完成系统。用户能够输入一个句子(至少一个单词,并以一个特殊的字符'#'结尾)。对于除'#'以外的每一个字符,您须要返回与已输入的句子部分前缀相同的前3个历史热门句子。具体规则以下:数组
一个句子的热度定义为用户输入彻底相同句子的次数。 返回的前3个热门句子应该按照热门程度排序(第一个是最热的)。若是几个句子的热度相同,则须要使用ascii代码顺序(先显示较小的一个)。 若是少于3个热门句子,那么就尽量多地返回。 当输入是一个特殊字符时,它意味着句子结束,在这种状况下,您须要返回一个空列表。 您的工做是实现如下功能:bash
构造函数:函数
AutocompleteSystem(String[] sentence, int[] times):这是构造函数。输入是历史数据。句子是由以前输入的句子组成的字符串数组。Times是输入一个句子的相应次数。您的系统应该记录这些历史数据。测试
如今,用户想要输入一个新句子。下面的函数将提供用户类型的下一个字符:动画
List input(char c):输入c是用户输入的下一个字符。字符只能是小写字母(“a”到“z”)、空格(“”)或特殊字符(“#”)。另外,前面输入的句子应该记录在系统中。输出将是前3个历史热门句子,它们的前缀与已经输入的句子部分相同。ui
例子: 操做:AutocompleteSystem(["i love you", "island","ironman", "i love leetcode"], [5,3,2,2]) 系统已经追踪到如下句子及其对应的时间:搜索引擎
"i love you" : 5 times "island" : 3 times "ironman" : 2 times "i love leetcode" : 2 timesspa
如今,用户开始另外一个搜索:设计
操做:输入(“i”) 输出:["i love you", "island","i love leetcode"] 解释: 有四个句子有前缀“i”。其中,《ironman》和《i love leetcode》有着相同的热度。既然“ ” ASCII码为32,“r”ASCII码为114,那么“i love leetcode”应该在“ironman”前面。此外,咱们只须要输出前3个热门句子,因此“ironman”将被忽略。
操做:输入(' ') 输出:[“i love you”,“i love leetcode”] 解释: 只有两个句子有前缀“i”。
操做:输入(' a ') 输出:[] 解释: 没有以“i a”为前缀的句子。
操做:输入(“#”) 输出:[] 解释: 用户完成输入后,在系统中将句子“i a”保存为历史句。下面的输入将被计算为新的搜索。
注意:
输入的句子老是以字母开头,以“#”结尾,两个单词之间只有一个空格。 要搜索的完整句子不会超过100个。包括历史数据在内的每句话的长度不会超过100句。 在编写测试用例时,即便是字符输入,也请使用双引号而不是单引号。 请记住重置在AutocompleteSystem类中声明的类变量,由于静态/类变量是跨多个测试用例持久化的。详情请点击这里。
设计一个搜索自动补全系统,它须要包含以下两个方法:
AutocompleteSystem(String[] sentences, int[] times): 输入句子sentences,及其出现次数times
List input(char c): 输入字符c能够是26个小写英文字母,也能够是空格,以'#'结尾。返回输入字符前缀对应频率最高的至多3个句子,频率相等时按字典序排列。
核心点:Trie(字典树)
利用字典树记录全部出现过的句子集合,利用字典保存每一个句子出现的次数。
题目的要求是补全的句子是按以前出现的频率排列的,高频率的出如今最上面,若是频率相同,就按字母顺序来显示。
频率 这种要求很容易想到 堆、优先队列、树、Map等知识点,这里涉及到 字典 与 树,那确定使用 字典树 能解决。
因此首先构造 Trie 的 trieNode 结构以及 insert 方法,构造完 trieNode 类后,再构造一个树的根节点。
因为每次都要输入一个字符,咱们能够用一个私有的 Node:curNode 来追踪当前的节点。
curNode 初始化为 root ,在每次输入完一个句子时,即输入的字符为‘#’时,咱们须要将其置为root。
同时还须要一个 string 类型 stn 来表示当前的搜索的句子。
每输入一个字符,首先检查是否是结尾标识“#”,若是是的话,将当前句子加入trie树,重置相关变量,返回空数组。
如不是,检查当前 TrieNode 对应的 child 是否含有 c 的对应节点。若是没有,将 curNode 置为 NULL 而且返回空数组。
若存在,将curNode 更新为c对应的节点,而且对curNode进行dfs。
dfs 时,咱们首先检查当前是否是一个完整的句子,若是是,将句子与其次数同时加入 priority_queue 中,而后对其 child 中可能存在的子节点进行 dfs 。
进行完 dfs 后,只须要取出前三个,须要注意的是,可能可选择的结果不满3个,因此要在 while 中多加入检测 q 为空的条件语句。
最后要将 q 中的全部元素都弹出。
动画是使用 AE 制做,体积比较大,有 32 M,没法使用GIF播放,所以采起视频播放形式,手机党慎点:)
感谢 Jun Chen 大佬提供动画技术支持,笔芯。
Markdown 不提供视频播放功能,请前往这里进行观看:)
class TrieNode{
public:
string str;
int cnt;
unordered_map<char, TrieNode*> child;
TrieNode(): str(""), cnt(0){};
};
struct cmp{
bool operator() (const pair<string, int> &p1, const pair<string, int> &p2){
return p1.second < p2.second || (p1.second == p2.second && p1.first > p2.first);
}
};
class AutocompleteSystem {
public:
AutocompleteSystem(vector<string> sentences, vector<int> times) {
root = new TrieNode();
for(int i = 0; i < sentences.size(); i++){
insert(sentences[i], times[i]);
}
curNode = root;
stn = "";
}
vector<string> input(char c) {
if(c == '#'){
insert(stn, 1);
stn.clear();
curNode = root;
return {};
}
stn.push_back(c);
if(curNode && curNode->child.count(c)){
curNode = curNode->child[c];
}else{
curNode = NULL;
return {};
}
dfs(curNode);
vector<string> ret;
int n = 3;
while(n > 0 && !q.empty()){
ret.push_back(q.top().first);
q.pop();
n--;
}
while(!q.empty()) q.pop();
return ret;
}
void dfs(TrieNode* n){
if(n->str != ""){
q.push({n->str, n->cnt});
}
for(auto p : n->child){
dfs(p.second);
}
}
void insert(string s, int cnt){
TrieNode* cur = root;
for(auto c : s){
if(cur->child.count(c) == 0){
cur->child[c] = new TrieNode();
}
cur = cur->child[c];
}
cur->str = s;
cur->cnt += cnt;
}
private:
TrieNode *root, *curNode;
string stn;
priority_queue<pair<string,int>, vector<pair<string, int>>, cmp > q;
};
复制代码
代码由小伙伴 Toby Qin 和 xiaodong 提供:)
class TrieNode:
def __init__(self):
self.children = dict()
self.sentences = set()
class AutocompleteSystem(object):
def __init__(self, sentences, times):
""" :type sentences: List[str] :type times: List[int] """
self.buffer = ''
self.stimes = collections.defaultdict(int)
self.trie = TrieNode()
for s, t in zip(sentences, times):
self.stimes[s] = t
self.addSentence(s)
self.tnode = self.trie
def input(self, c):
""" :type c: str :rtype: List[str] """
ans = []
if c != '#':
self.buffer += c
if self.tnode: self.tnode = self.tnode.children.get(c)
if self.tnode: ans = sorted(self.tnode.sentences, key=lambda x: (-self.stimes[x], x))[:3]
else:
self.stimes[self.buffer] += 1
self.addSentence(self.buffer)
self.buffer = ''
self.tnode = self.trie
return ans
def addSentence(self, sentence):
node = self.trie
for letter in sentence:
child = node.children.get(letter)
if child is None:
child = TrieNode()
node.children[letter] = child
node = child
child.sentences.add(sentence)
复制代码