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
  • wordprefix 仅由小写英文字母组成
  • insertsearchstartsWith 调用次数 总计 不超过 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),所以搜索不到。
相关推荐
Sayuanni%31 小时前
初阶_多线程2(线程安全)
java
niuniudengdeng1 小时前
一种基于XRF实景建模与AI世界生成的一步闭式解光线追踪视觉生成模型
人工智能·数学·算法
mit6.8241 小时前
树上滑窗
算法
Howie Zphile1 小时前
# 组织增熵与全面预算管理的持续优化
java·大数据·数据库
清水白石0081 小时前
从频繁“握手”到高效通行:Python 数据库连接池深度解析与调优实战
开发语言·数据库·python
我命由我123451 小时前
C++ EasyX 开发,MessageBox 函数参数问题:“const char *“ 类型的实参与 “LPCWSTR“ 类型的形参不兼容
c语言·开发语言·c++·后端·学习·visualstudio·visual studio
芒克芒克1 小时前
深入浅出BlockingQueue(二)
java
识君啊1 小时前
Java 栈 - 附LeetCode 经典题解
java·数据结构·leetcode·deque··stack·lifo
shehuiyuelaiyuehao1 小时前
关于hashset和hashmap,还有treeset和treemap,四个的关系
java·开发语言