LeetCode 208 是我用 JavaScript 刷算法题到第十九天时遇到的一道结构题。在这之前我脑子里对 Trie 的印象只是“字典树”“可以做前缀查找”,但真到要求自己在几十行代码里实现它,我发现有几个点没想清楚根本写不顺:节点上该存字符还是边存字符?路径走到头之后靠什么确认“这是一个完整单词”?搜索单词和搜索前缀到底差在哪一行?这篇分享就用 JavaScript 把 208 完整做一遍,连同那些题解里很少展开的容器选型、删除扩展和自动补全一起聊透。不管你是准备面试,还是想在项目里做输入框联想,都可以拿这篇文章当参考。
1. 为什么前缀匹配用“一张词表”会力不从心
1.1 只存完整单词:查单词很快,查前缀很慢
很多人看到 208 的第一反应是:我直接用 Set 把所有单词存起来不就行了吗?查询某个单词是否存在是 O(1),为什么不这样做?
单独看“单词是否存在”,Set 确实没问题。但题目明确要求了 startsWith(prefix),也就是判断是否存在某个以指定前缀开头的单词。这时候问题就暴露了:Set 只保存完整的单词,它没有办法回答“这个前缀片段后面还可能接什么”。
在没有额外索引的情况下,你只能遍历整张词表:
javascript复制class WordStore {
constructor(words) {
this.words = new Set(words);
}
hasWord(word) {
return this.words.has(word);
}
hasPrefix(prefix) {
for (const word of this.words) {
if (word.startsWith(prefix)) return true;
}
return false;
}
}
这个实现本身没问题,问题在复杂度上。如果词表里有 10 万个词,每次 hasPrefix 最坏都要遍历 10 万次,每次还要做一次字符串前缀比较,整体就是 O(词表长度 × 单词平均长度)。在 LeetCode 的测试数据面前,这个复杂度几乎不可能通过。
更重要的是,真实业务里的输入框联想不是只问“有没有这个前缀”,而是要把“所有以这个前缀开头的词”都列出来。用 Set 配合遍历虽然也能做到,但代价更大,而且代码写起来很别扭。
1.2 把“所有前缀”也塞进 Set 是一条歧路
如果只追求前缀匹配快,还有一个看起来很聪明的思路:插入单词的时候,顺便把它的所有前缀都存进 Set。
例如插入 apple,就存 a、ap、app、appl、apple。这样查询前缀时依然是哈希查找,O(1) 就出结果。
javascript复制class PrefixSet {
constructor() {
this.map = new Set();
}
insert(word) {
for (let i = 1; i <= wo
