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 就是单词或者前缀的长度。空间的话最坏情况就是所有单词都没有公共前缀,存下所有字符就行。

最后唠两句

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

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

相关推荐
To_OC8 小时前
LC 51 N 皇后:我以为难的是回溯,结果栽在了对角线下标
javascript·算法·leetcode
2401_841495649 小时前
【操作系统】进程同步与互斥实验报告
c++·算法·操作系统·进程·并发·同步·互斥
小林ixn10 小时前
从零到一理解 React 父子组件通信:手写一个 Todo 应用带你彻底搞懂单向数据流
前端·javascript·react.js
窝子面12 小时前
手搓最简前后端协作-node
javascript·数据库
爱刷碗的苏泓舒13 小时前
PPP-AR 中的参考星选取:数学原理、评价指标与切换处理
算法·gnss·模糊度固定·ppp-ar·星间单差·参考星·卫星端偏差
kyriewen13 小时前
我让AI给前端项目做了一次完整的Code Review——它和人类的差距,比我想的大得多
前端·javascript·ai编程
এ慕ོ冬℘゜13 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
sugar__salt14 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
SmartBoyW15 小时前
CSS弹性布局(Flexbox)学习笔记:从零开始搞懂弹性布局
前端·javascript