文章目录
实现 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
思路一
class TrieNode {
constructor() {
this.children = {}; // 存储子节点
this.isEndOfWord = false; // 标记是否为单词的结束
}
}
class Trie {
constructor() {
this.root = new TrieNode(); // 初始化根节点
}
// 插入单词
insert(word) {
this._insertRecursive(this.root, word, 0);
}
_insertRecursive(node, word, index) {
if (index === word.length) {
node.isEndOfWord = true; // 记录单词的结束
return;
}
const char = word[index];
if (!node.children[char]) {
node.children[char] = new TrieNode(); // 创建新节点
}
// 递归插入下一个字符
this._insertRecursive(node.children[char], word, index + 1);
}
// 搜索单词
search(word) {
return this._searchRecursive(this.root, word, 0);
}
_searchRecursive(node, word, index) {
if (index === word.length) {
return node.isEndOfWord; // 检查是否到达单词的结束
}
const char = word[index];
if (!node.children[char]) {
return false; // 字符不存在,返回 false
}
// 递归搜索下一个字符
return this._searchRecursive(node.children[char], word, index + 1);
}
// 检查前缀
startsWith(prefix) {
return this._startsWithRecursive(this.root, prefix, 0);
}
_startsWithRecursive(node, prefix, index) {
if (index === prefix.length) {
return true; // 前缀存在
}
const char = prefix[index];
if (!node.children[char]) {
return false; // 字符不存在,返回 false
}
// 递归检查下一个字符
return this._startsWithRecursive(node.children[char], prefix, index + 1);
}
}
讲解
- TrieNode 类:表示每个节点,包含一个 children 对象(用于存储子节点)和一个 isEndOfWord 布尔值(标记是否为单词的结束)。
- Trie 类:包含根节点并实现了插入、搜索和前缀检查的方法。
insert 方法通过 _insertRecursive 递归地插入每个字符。
search 方法通过 _searchRecursive 检查单词是否存在。
startsWith 方法通过 _startsWithRecursive 检查前缀是否存在。
思路二
// Trie 结构定义
var Trie = function() {
// 初始化 Trie 结构
this.root = {};
};
// 插入单词到 Trie 中
Trie.prototype.insert = function(word) {
let node = this.root;
for (let char of word) {
// 如果当前节点没有这个字符作为子节点,则创建新的子节点
if (!node[char]) {
node[char] = {};
}
// 移动到下一个节点
node = node[char];
}
// 设置单词结束标志
node.isEnd = true;
};
// 搜索单词是否存在于 Trie 中
Trie.prototype.search = function(word) {
let node = this.root;
for (let char of word) {
// 如果在某处找不到对应的字符,则返回 false
if (!node[char]) {
return false;
}
// 移动到下一个节点
node = node[char];
}
// 返回是否到达单词结束标志
return node.isEnd === true;
};
// 检查 Trie 是否包含以 prefix 开头的任何单词
Trie.prototype.startsWith = function(prefix) {
let node = this.root;
for (let char of prefix) {
// 如果在某处找不到对应的字符,则返回 false
if (!node[char]) {
return false;
}
// 移动到下一个节点
node = node[char];
}
// 到达前缀的末尾,返回 true
return true;
};
讲解
- Trie 结构: Trie 是一种树形结构,用于高效地存储和检索字符串。每个节点代表一个字符,从根节点到任意一个叶子节点的路径上的所有字符组合成一个字符串。通常,我们会用一个标志位来表示该节点是否代表了一个单词的结束。
- 插入操作 (insert): 遍历字符串中的每个字符,如果当前节点不存在对应的子节点,则创建一个新的子节点;否则,移动到对应的子节点继续遍历。最后,在最后一个字符的节点上设置标志位表示一个单词的结束。
- 搜索操作 (search): 类似于插入操作,遍历字符串中的每个字符,如果在某个点字符对应的子节点不存在,则返回false。如果完整遍历字符串并且最后一个字符的节点有单词结束的标志,则返回 true 。
- 前缀查找 (startsWith): 同样地,遍历前缀中的每个字符,如果在某个点字符对应的子节点不存在,则返回 false 。如果完整遍历前缀字符串,则返回 true ,因为我们只关心是否存在这个前缀,而不关心它是否构成一个完整的单词。