LeetCode 208中等Trie
实现 Trie (前缀树) 图解题解
前缀树把共享前缀变成共享路径,查词和查前缀的区别只在终点。
字典树就像一棵单词地铁线网:从根出发,每个字母是一站,拼写相同前缀的单词共享前面同一段线路,到分叉处才各走各的。查一个词就是顺着字母一站站坐过去,看终点是否挂着「这里是个完整单词」的牌子;查前缀则只需确认路能走通,不看终点有没有牌子。
这道题到底在问什么
请你实现 Trie 类: Trie() 初始化前缀树对象。 void insert(String word) 向前缀树中插入字符串 word 。 boolean search(String word) 如果字符串 word 在前缀树中,返回 true (即,在检索之前已经插入);否则,返回 false 。 boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
- 操作
- insert/search/startsWith
- 输出
- 按操作返回
最优解:一步一步想明白
- 3下面 9 步动画会按主解代码推进,而不是泛泛讲题型。
- 4前缀树:每条边一个字符,路径=一个前缀insert("app"):逐字符建一条路径 root→a→p→p。末位 p2 打上「词尾」标记(绿色 done),表示「app 是一个完整单词」。
- 5app 已存在 → 只新建 l、einsert("apple"):前三位 app 复用已有节点(不重建),只新建 l、e。末位 e 也标词尾(绿色 done)。这就是 Trie 共享前缀省空间的关键。
- 6沿路径走 + 检查终点词尾标记search("app"):沿 a→p→p 走到末位 p2,它是绿色词尾节点 → 是完整单词 → 返回 true。
- 7终点没有词尾标记search("ap"):走到 p1 停下(高亮的当前节点),它不是绿色词尾;真正的词尾是它后面的 p2(绿)。所以 "ap" 不是完整单词 → 返回 false。
- 8前缀查询不检查词尾startsWith("ap"):和上一步走到完全相同的 p1,但这次只问「路径走得通吗」——能走到就行,不看后面 p2 是不是绿色词尾 → 返回 true。search 与 startsWith 唯一的区别就在这里。
- 11记住这题的代码骨架:题意约束先落到状态变量,再用循环或递归维护它。
- 16把这题练到能复述后,再去同类题里迁移:先复用状态定义,再调整边界条件。
⚠️ 容易写错的地方
✗ 错:search 只判断路径存在
✓ 对:还要检查 end 标记
app 存在不代表 apple 存在
✗ 错:startsWith 检查 end
✓ 对:只要路径存在即可
前缀不一定是完整单词
完整代码(Python / C++ / Java)
Python
class Trie:
def __init__(self):
self.root = {}
def insert(self, word):
node = self.root
for ch in word:
node = node.setdefault(ch, {})
node['#'] = True
def search(self, word):
node = self.root
for ch in word:
if ch not in node:
return False
node = node[ch]
return '#' in node
def startsWith(self, prefix):
node = self.root
for ch in prefix:
if ch not in node:
return False
node = node[ch]
return TrueC++
class Trie {
struct Node { Node* ch[26] = {}; bool end = false; };
Node* root = new Node();
Node* find(const string& w) {
Node* node = root;
for (char c : w) { if (!node->ch[c-'a']) return nullptr; node = node->ch[c-'a']; }
return node;
}
public:
void insert(string word) {
Node* node = root;
for (char c : word) { if (!node->ch[c-'a']) node->ch[c-'a'] = new Node(); node = node->ch[c-'a']; }
node->end = true;
}
bool search(string word) { Node* n = find(word); return n && n->end; }
bool startsWith(string prefix) { return find(prefix) != nullptr; }
};Java
class Trie {
Node root = new Node();
class Node { Node[] ch = new Node[26]; boolean end = false; }
private Node find(String w) {
Node node = root;
for (char c : w.toCharArray()) { if (node.ch[c-'a'] == null) return null; node = node.ch[c-'a']; }
return node;
}
public void insert(String word) {
Node node = root;
for (char c : word.toCharArray()) { if (node.ch[c-'a'] == null) node.ch[c-'a'] = new Node(); node = node.ch[c-'a']; }
node.end = true;
}
public boolean search(String word) { Node n = find(word); return n != null && n.end; }
public boolean startsWith(String prefix) { return find(prefix) != null; }
}复杂度
时间复杂度
O(L)
L 为单词或前缀长度
空间复杂度
O(total chars)
保存前缀树节点
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 实现 Trie (前缀树) 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
insert/search/startsWith 复杂度?+
都只沿单词长度 L 走一遍,O(L)。
Trie 比哈希集合好在哪?+
Trie 支持前缀查询(startsWith)、共享前缀省空间;哈希只能查完整词、不能高效前缀匹配。
空间消耗多少?+
O(所有插入字符串的总字符数);每个节点最多 26 个孩子(小写字母)。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。