642. Design Search Autocomplete System - cocoder39/coco39_LC GitHub Wiki

642. Design Search Autocomplete System

I attempted to create a separate Trie class. It would then become awkward to access the root node of Trie but I was not able to avoid doing so.

Time complexity: assuming inserting N sentences, and the length of each sentence is M

  • initialization: O(M * N)
  • input: for #, O(M); otherwise O(27 ^ M) since c is a lowercase English letter or space (can pre-compute the top search results then store it along with each node)
class TrieNode:
    def __init__(self):
        self.children = collections.defaultdict(TrieNode)
        self.sentence = None
        self.freq = 0
        
class AutocompleteSystem:

    def __init__(self, sentences: List[str], times: List[int]):
        self.root = TrieNode()
        self.term = ''
        for i, sentence in enumerate(sentences):
            self.__insert(sentence, times[i])
            
    def input(self, c: str) -> List[str]:
        res = []
        if c != '#':
            self.term += c
            res = self.__search(self.term)
        else:
            self.__insert(self.term, 1)
            self.term = ''
        return [s for t, s in sorted(res)[:3]]
        
    def __insert(self, sentence, times):
        cur = self.root
        for ch in sentence:
            cur = cur.children[ch]
        cur.sentence = sentence
        cur.freq -= times
    
    def __search(self, term):
        cur = self.root
        for ch in term:
            if ch not in cur.children:
                return []
            cur = cur.children[ch]
        return self.__dfs(cur)
    
    def __dfs(self, node):
        res = []
        if node:
            if node.sentence:
                res.append((node.freq, node.sentence))
            for child in node.children:
                res.extend(self.__dfs(node.children[child]))
        return res

Option 2 (better): get top k while inserting

Time complexity: assuming inserting N sentences, and the length of each sentence is M

  • initialization: O(M * N)
  • input: O(M)
class TrieNode:
    def __init__(self):
        self.children = collections.defaultdict(TrieNode)
        self.sentence = None
        self.freq = 0
        self.topK = {} # sentence to freq
        
class AutocompleteSystem:

    def __init__(self, sentences: List[str], times: List[int]):
        self.root = TrieNode()
        self.term = ''
        for i, sentence in enumerate(sentences):
            self.__update(sentence, times[i], self.root, 0)
    
    def __update(self, sentence, times, node, idx):        
        if idx == len(sentence):
            node.sentence = sentence
            node.freq -= times
            node.topK[sentence] = node.freq
        else:
            ch = sentence[idx]
            child = node.children[ch]
            candidates = self.__update(sentence, times, child, idx+1)
            for s, t in candidates.items():
                node.topK[s] = t
            
        res = sorted([(t, s) for s, t in node.topK.items()])[:3]
        node.topK = {s:t for t, s in res}
        return node.topK
        
    def __search(self, term):
        cur = self.root
        for ch in term:
            if ch not in cur.children:
                return []
            cur = cur.children[ch]
        return [s for s, t in cur.topK.items()]
            
    def input(self, c: str) -> List[str]:
        if c != '#':
            self.term += c
            return self.__search(self.term)
        else:
            self.__update(self.term, 1, self.root, 0)
            self.term = ''
            return []