实现 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
次
代码结果
运行时间: 78 ms, 内存: 27.8 MB
// Trie class implementation in Java using Java Streams
// This is a Trie data structure which is used to efficiently store and retrieve keys in a dataset of strings.
// Each node in the Trie represents a single character of a word.
import java.util.HashMap;
import java.util.Map;
class TrieNode {
public Map<Character, TrieNode> children = new HashMap<>();
public boolean isEndOfWord = false;
}
public class Trie {
private final TrieNode root;
// Constructor to initialize the root of the Trie
public Trie() {
root = new TrieNode();
}
// Inserts a word into the trie.
public void insert(String word) {
TrieNode node = root;
word.chars().mapToObj(c -> (char) c).forEach(c -> {
node.children.putIfAbsent(c, new TrieNode());
node = node.children.get(c);
});
node.isEndOfWord = true;
}
// Returns if the word is in the trie.
public boolean search(String word) {
TrieNode node = root;
return word.chars().mapToObj(c -> (char) c).allMatch(c -> {
if (node.children.get(c) == null) {
return false;
}
node = node.children.get(c);
return true;
}) && node.isEndOfWord;
}
// Returns if there is any word in the trie that starts with the given prefix.
public boolean startsWith(String prefix) {
TrieNode node = root;
return prefix.chars().mapToObj(c -> (char) c).allMatch(c -> {
if (node.children.get(c) == null) {
return false;
}
node = node.children.get(c);
return true;
});
}
}
解释
方法:
这个题解使用 Trie(字典树)数据结构来实现字符串的快速插入、查询和前缀匹配。Trie 中的每个节点表示一个字符,从根节点到某一节点的路径表示该节点对应的字符串。在插入字符串时,从根节点出发,沿着字符串的字符遍历 Trie,如果对应的子节点不存在,则创建一个新的子节点。查询时,同样沿着字符串的字符遍历 Trie,如果某个字符对应的子节点不存在,则说明 Trie 中不包含该字符串。如果遍历到最后一个字符的节点,且该节点是一个词的末尾(可用一个特殊字符 '#' 表示),则说明 Trie 中存在该字符串。前缀匹配时,只需判断能否在 Trie 中找到前缀对应的路径即可。
时间复杂度:
O(m),其中 m 为字符串的长度
空间复杂度:
O(n * k),其中 n 为字符串的平均长度,k 为字符串的数量
代码细节讲解
🦆
在`Trie`类的初始化中,你使用了一个字典来表示根节点。请问在实际情况中,使用字典存储节点与使用固定大小数组存储节点相比,有哪些优缺点?
▷🦆
你提到在插入单词的末尾节点处加入一个特殊字符`#`来标识单词的结束。请问这种方法与在节点中使用一个布尔标志`isEnd`来标识结束有什么不同?哪一种方法更优?
▷🦆
在`search`函数中,你检查了`#`字符是否存在于最后一个字符的子节点字典中。如果存在其他方法判断一个字符串是否被完整存储在Trie中,这些方法可能是什么?
▷相关问题
添加与搜索单词 - 数据结构设计
请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 WordDictionary
:
WordDictionary()
初始化词典对象void addWord(word)
将word
添加到数据结构中,之后可以对它进行匹配bool search(word)
如果数据结构中存在字符串与word
匹配,则返回true
;否则,返回false
。word
中可能包含一些'.'
,每个.
都可以表示任何一个字母。
示例:
输入: ["WordDictionary","addWord","addWord","addWord","search","search","search","search"] [[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]] 输出: [null,null,null,null,false,true,true,true] 解释: WordDictionary wordDictionary = new WordDictionary(); wordDictionary.addWord("bad"); wordDictionary.addWord("dad"); wordDictionary.addWord("mad"); wordDictionary.search("pad"); // 返回 False wordDictionary.search("bad"); // 返回 True wordDictionary.search(".ad"); // 返回 True wordDictionary.search("b.."); // 返回 True
提示:
1 <= word.length <= 25
addWord
中的word
由小写英文字母组成search
中的word
由 '.' 或小写英文字母组成- 最多调用
104
次addWord
和search
单词替换
在英语中,我们有一个叫做 词根
(root) 的概念,可以词根后面添加其他一些词组成另一个较长的单词——我们称这个词为 继承词
(successor)。例如,词根an
,跟随着单词 other
(其他),可以形成新的单词 another
(另一个)。
现在,给定一个由许多词根组成的词典 dictionary
和一个用空格分隔单词形成的句子 sentence
。你需要将句子中的所有继承词用词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。
你需要输出替换之后的句子。
示例 1:
输入:dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery" 输出:"the cat was rat by the bat"
示例 2:
输入:dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs" 输出:"a a b c"
提示:
1 <= dictionary.length <= 1000
1 <= dictionary[i].length <= 100
dictionary[i]
仅由小写字母组成。1 <= sentence.length <= 10^6
sentence
仅由小写字母和空格组成。sentence
中单词的总量在范围[1, 1000]
内。sentence
中每个单词的长度在范围[1, 1000]
内。sentence
中单词之间由一个空格隔开。sentence
没有前导或尾随空格。
实现一个魔法字典
设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。
实现 MagicDictionary
类:
MagicDictionary()
初始化对象void buildDict(String[] dictionary)
使用字符串数组dictionary
设定该数据结构,dictionary
中的字符串互不相同bool search(String searchWord)
给定一个字符串searchWord
,判定能否只将字符串中 一个 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回true
;否则,返回false
。
示例:
输入 ["MagicDictionary", "buildDict", "search", "search", "search", "search"] [[], [["hello", "leetcode"]], ["hello"], ["hhllo"], ["hell"], ["leetcoded"]] 输出 [null, null, false, true, false, false] 解释 MagicDictionary magicDictionary = new MagicDictionary(); magicDictionary.buildDict(["hello", "leetcode"]); magicDictionary.search("hello"); // 返回 False magicDictionary.search("hhllo"); // 将第二个 'h' 替换为 'e' 可以匹配 "hello" ,所以返回 True magicDictionary.search("hell"); // 返回 False magicDictionary.search("leetcoded"); // 返回 False
提示:
1 <= dictionary.length <= 100
1 <= dictionary[i].length <= 100
dictionary[i]
仅由小写英文字母组成dictionary
中的所有字符串 互不相同1 <= searchWord.length <= 100
searchWord
仅由小写英文字母组成buildDict
仅在search
之前调用一次- 最多调用
100
次search