LC 208 实现 Trie 前缀树:曾被名字劝退,写完发现是送分题

不知道你有没有这种感觉,一听到 "前缀树""字典树" 这种听起来很专业的名词,就先怂了一半。我之前刷到这道题的时候,盯着 "Trie" 这个单词看了半天,连发音都不确定,迟迟不敢下手。真硬着头皮写完才发现,害,就这?

题目说啥呢

题目的要求很简单,让我们自己实现一个 Trie 类。就三个功能:往里面插单词、查某个单词存不存在、查有没有单词以某个前缀开头。

看着平平无奇,用普通集合还真不好高效实现。

我一开始的偷懒写法

刚看题我还想钻空子。心想不就是存字符串吗,用个 Set 全装起来不就完了?search 直接调用 has 方法,startsWith 就遍历所有单词挨个判断。

小用例跑起来确实没问题,但仔细看数据规模,调用次数最多三万次。每次查前缀都遍历一遍所有单词,时间直接爆炸,而且完全偏离了这道题的考点。老老实实写正解吧。

前缀树到底是啥

其实前缀树的逻辑特别好懂。说白了就是一棵树,每个节点代表一个字母。从根节点往下走,一条路径拼起来就是一个单词。比如插入 "apple",就是根节点 → a → p → p → l → e 这么一条路。

光有路径还不够,得区分 "完整单词" 和 "前缀"。比如存了 "apple" 之后,"app" 是前缀但不是完整单词。所以每个节点还要加个小标记,告诉我们走到这里是不是一个单词的结尾。

这样不管是插入还是查询,顺着单词的字母一个个往下走就行,效率特别高。

代码实现

我用对象来存子节点,写起来好懂,不容易写错。

javascript 复制代码
var Trie = function() {
    // 初始化根节点,存子节点集合 + 是否是单词结尾
    this.root = {
        children: {},
        isEnd: false
    };
};

Trie.prototype.insert = function(word) {
    // 每次操作都从根节点出发,别写外面去了,我踩过这个坑
    let node = this.root;
    for (let c of word) {
        // 没有这个字母的子节点,就新建一个
        if (!node.children[c]) {
            node.children[c] = {
                children: {},
                isEnd: false
            };
        }
        // 走到下一个节点
        node = node.children[c];
    }
    // 走完整个单词,标记当前是单词结尾
    node.isEnd = true; // 别问我怎么知道的,忘写这行我debug了十分钟
};

Trie.prototype.search = function(word) {
    let node = this.root;
    for (let c of word) {
        // 走不通了,说明根本没这个单词
        if (!node.children[c]) {
            return false;
        }
        node = node.children[c];
    }
    // 走完字母还不够,必须是单词结尾才算匹配
    // 比如存了apple,搜app能走完路径,但不是完整单词
    return node.isEnd;
};

Trie.prototype.startsWith = function(prefix) {
    let node = this.root;
    for (let c of prefix) {
        if (!node.children[c]) {
            return false;
        }
        node = node.children[c];
    }
    // 前缀不用管结尾,能走完所有字母就说明存在
    return true;
};

我踩过的两个坑

说两个我写的时候犯的傻错误,你们别再踩了。第一个就是 insert 最后忘加 isEnd 标记。结果搜什么单词都返回 false,我对着代码看了好几圈都没发现问题。最后反应过来的时候,真想给自己一下。

第二个坑更傻,我把当前节点 node 写到了构造函数里,当成全局变量了。插完一个单词,下一次插入就从上一个单词的结尾开始走,结果全乱套。记住,每次操作都要从 root 重新开始遍历。

复杂度分析

时间复杂度挺好算的,插入、搜索、查前缀都是 O (n),n 就是单词或者前缀的长度。空间的话最坏情况就是所有单词都没有公共前缀,存下所有字符就行。

最后唠两句

说真的,这题就是标准的 "纸老虎"。名字听起来吓人,实则逻辑特别清晰,写完一遍就能记住。它也是前缀树的模板题,搞懂了之后,什么自动补全、拼写检查的底层逻辑,你就都有数了。

你第一次写前缀树的时候踩过什么有意思的坑?或者有更简洁的写法?评论区聊聊呗,我都会回来看的。如果觉得这篇对你有用,点个赞让更多小伙伴看到呀~

相关推荐
天渺工作室2 小时前
实现一个adblock/adblock plus等浏览器广告拦截器检测插件
前端·javascript
BadBadBad__AK3 小时前
线段树维护区间 k 次方和
c++·数学·算法·stl
kyriewen10 小时前
2026 年了,还在用 Node.js?Bun 迁移实战:20 分钟搞定,附踩坑记录
前端·javascript·node.js
_清歌15 小时前
DSpark 深度解读:DeepSeek-V4 如何用「半自回归」把推理速度提升 85%
算法
minglie15 小时前
一个置换问题
javascript
统计实现局15 小时前
SVD 的三步走:双对角化、Givens 收敛、排序
算法
躬行见万象15 小时前
《VLA 系列》UniLab 强化训练 | G1 机器人 |复现
算法
统计实现局15 小时前
对称不定分解(Bunch-Kaufman):为什么 Cholesky 不够用
算法