前缀树是一个树结构,对传入的字符串,我们尽力在树中找到最深的情况,并替换为对应的字符串。最后将所有字符的返回结果拼合在一起,就得到最终的结果了。这有点儿像原子性,我们不会停留在某个中间态,只需一个执行就可以得到最终结果。并且,原来的方案中,长度为 n 的字符串匹配 m 个规则,需要 O(n*m) 的时间复杂度,前缀树则只用 O(n) 的时间复杂度。
在执行替换之前,我们需要先将字典转换为前缀树结构:
1 2 3 4 5 6 7 8 9
root = new TrieNode() defbuild_trie(replacements): for key, val in replacements.items(): node = root for char in key: # tree 中的每个结点都是一个字符 if char notin node.children:# 当前结点没有 char 字符的子结点,开辟新子结点 node.children[char] = TrieNode() node = node.children[char] node.value = val
其中,TrieNode 由 children(dict)和value(str)组成。
对于上面的例子,我们会得到如下的树
1 2 3 4 5 6 7 8 9
graph LR e1(e) e2(e) uei2(uei) root-->e1==等于==>ee e1-->e2==等于==>ei root-->u==等于==>uei2 root-->ə==等于==>e root-->ẽ==等于==>en
defreplace_with_dict(text: str, replacements: dict[str, str], max_key_len: int = 3) -> str: i = 0 res = []
# 预取下所有可能的 key 长度,避免每次都 max() key_lengths = sorted({len(k) for k in replacements.keys()}, reverse=True)
while i < len(text): # 尝试最长的 key for L in key_lengths: if L == 0: continue snippet = text[i:i+L] if snippet in replacements: res.append(replacements[snippet]) i += L break else: # 没有任何匹配 res.append(text[i]) i += 1