LeetCode 642 號問題:設計搜索自動補全系統

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 不提供視頻播放功能,請前往這裏進行觀看:)

參考代碼

C++

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;
    
};

複製代碼

Python

代碼由小夥伴 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)
複製代碼

代碼截圖

C++代碼
Python代碼
相關文章
相關標籤/搜索