208. 实现 Trie (前缀树)

208. 实现 Trie (前缀树)

中等

Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。

请你实现 Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word 。
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。

示例:

复制代码
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]

解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple");   // 返回 True
trie.search("app");     // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app");     // 返回 True

提示:

  • 1 <= word.length, prefix.length <= 2000
  • word 和 prefix 仅由小写英文字母组成
  • insert、search 和 startsWith 调用次数 总计 不超过 3 * 104 次

📝 核心笔记:实现 Trie (前缀树 / 字典树)

1. 核心思想 (一句话总结)

"共用前缀,节点存路不存值,唯有终点做标记。"

Trie 是一棵 26 叉树(假设只处理小写字母)。

  • 节点 :通常不直接存字符,而是通过"数组索引"隐含字符(son[0] 代表 'a')。
  • 路径:从 Root 往下走,经过的路径连起来就是一个字符串。
  • 标记 :必须有一个 boolean isEnd,区分"这里是单词结尾"还是"只是路过"。
2. 算法流程 (三步走)
  1. 建树 (Node) :每个节点带一个 Node[26] 数组和一个 boolean end 标记。
  2. 插入 (Insert):
    • 遍历字符串,如果对应字符的坑位是 null,就 new 一个新节点铺路。
    • 走到最后一个字符,把当前节点的 end 设为 true。
  1. 查找 (Find - 核心):
    • 遍历字符串,如果半路遇到 null,直接返回"不存在"。
    • 走完字符串后,检查当前节点的 end 标记,决定是"完全匹配"还是"前缀匹配"。
🔍 代码回忆清单 (带注释版)
复制代码
// 题目:LC 208. Implement Trie (Prefix Tree)
class Trie {
    private static class Node {
        // 关键点1:26叉树,利用数组下标 0-25 映射 'a'-'z'
        Node[] son = new Node[26];
        boolean end = false; // 标记是否是单词结尾
    }

    private final Node root = new Node(); // 哑根节点 (Dummy Root)

    public void insert(String word) {
        Node cur = root;
        for (char c : word.toCharArray()) {
            int idx = c - 'a'; // 字符转下标
            if (cur.son[idx] == null) {
                cur.son[idx] = new Node(); // 没路就铺路
            }
            //当前节点的第 idx 个子节点(对应字母 'a'+idx)
            cur = cur.son[idx]; // 往下走
        }
        cur.end = true; // 关键点2:插旗,表示这里是个完整的词
    }

    // 搜索完整单词
    public boolean search(String word) {
        return find(word) == 2;
    }

    // 搜索前缀
    public boolean startsWith(String prefix) {
        return find(prefix) != 0; // 只要不是 0 (没断路),1或2都算前缀存在
    }

    // ⭐️ 高级写法:统一处理 search 和 startsWith
    // 返回值含义:0=不存在, 1=是前缀但不是单词, 2=是单词(也是前缀)
    private int find(String word) {
        Node cur = root;
        for (char c : word.toCharArray()) {
            int idx = c - 'a';
            if (cur.son[idx] == null) {
                return 0; // 此路不通
            }
            cur = cur.son[idx];
        }
        // 走到头了,看 end 标记
        return cur.end ? 2 : 1; 
    }
}
⚡ 快速复习 CheckList (易错点)
  • 字符映射做了吗?
    • c - 'a' 是基本操作。如果不做减法直接用 c 当下标,数组会越界(除非开 Node[128])。
  • Root 节点存值吗?
    • Root 节点通常是空的,不代表任何字符。第 1 层节点代表字符串的第 1 个字符。
  • isEnd****标记的作用?
    • 没有这个标记,你无法区分插入的是 "apple" 还是 "app"。
    • 搜 "app" 时,路径存在,但如果 end 是 false,说明字典里只有 "apple",没有 "app" 这个词。
  • 复杂度是多少?
    • 时间:O(L),L 是单词长度。跟字典里有多少词无关,极快。
    • 空间:O(N \times 26),N 是总字符数。空间换时间,比较费内存。
🖼️ 脑图模型 (自动补全)

想象你在手机输入法里打字:

  1. 输入 "h" -> 走到 root.son['h']。
  2. 输入 "e" -> 走到下一层的 ['e']。
  3. 输入 "y" -> 走到下一层的 ['y']。
    • 这个节点 end = true (字典里有 "hey")。
  1. 如果你只输入 "he":
    • 路径存在 (find 返回 1),显示联想词 "hey", "hello"...
    • 但字典里可能没有单独的 "he" 这个词 (find 返回 1 != 2),所以搜索不到。
相关推荐
垆边人似月.4 小时前
华为机试题 :有效的括号
c++·算法·华为
2601_966949654 小时前
多市场量化策略的数据接口应该如何设计:从数据层架构到策略接入
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
坤坤子吖4 小时前
C++智能指针:RAII、shared_ptr与内存泄漏
开发语言·c++·笔记·学习
奋斗的阿狸_19865 小时前
ESP32-S3 + ES7210 四通道麦克风录音上传方案
c语言·开发语言·fpga开发
夜之眷属5 小时前
Core dump 崩溃排查:JVM 宕机后,那份 core 文件怎么用 gdb 还原现场
java·运维·服务器·jvm
weixin199701080165 小时前
《1688图片空间API踩坑:img.upload 与 album.* 的防盗链与CDN缓存问题》(附Python源码)
开发语言·python·缓存
波加曼大王5 小时前
# vLLM不要迷信PagedAttention神话,聊聊线上藏着的内部碎片陷阱
java·架构
SL_staff5 小时前
目标健康度自检清单:开发者视角下的目标-计划-任务链路断点诊断
java·开源·github
A加ITIL教练长河achotsao5 小时前
ITIL Master 战略领导者课后系列分享:AI编程正在重新划分研发与运维的边界
算法
hahaha60165 小时前
FPGA+ARM实现AWB的全流程
人工智能·嵌入式硬件·算法·计算机视觉·fpga开发