题目描述
请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 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"); // return False
wordDictionary.search("bad"); // return True
wordDictionary.search(".ad"); // return True
wordDictionary.search("b.."); // return True
解题思路
本题可以使用字典树(Trie)来解决。字典树是一种专门用于处理字符串集合的数据结构,它能够高效地进行插入、查找等操作。
在这道题中,我们可以使用字典树来存储添加的单词。对于每个节点,除了存储字符外,还需要一个标记来表示该节点是否是一个单词的结束节点。然后,在搜索操作时,我们可以通过递归地遍历字典树来判断当前字符串是否匹配。
具体实现方式为:
- 在 TrieNode 类中定义一个 children 数组,用于存储子节点。
- 在 addWord 方法中,从根节点开始遍历待添加的单词的字符,如果当前字符不存在于当前节点的子节点中,则创建新的节点,并将新节点添加到当前节点的子节点数组中;如果当前字符已经存在于当前节点的子节点中,则直接移动到下一个节点。
- 在 search 方法中,从根节点开始遍历待搜索的字符串的字符,如果当前字符是 '.',则需要递归地遍历当前节点的所有子节点;如果当前字符不是 '.',则直接找到与当前字符对应的子节点,并继续向下遍历。
- 在递归遍历时,需要注意边界条件,即当遍历到字符串的最后一个字符时,需要判断当前节点是否为单词的结束节点。
代码实现
class TrieNode {
children: Map<string, TrieNode>;
isEndOfWord: boolean;
constructor() {
this.children = new Map();
this.isEndOfWord = false;
}
}
class WordDictionary {
root: TrieNode;
constructor() {
this.root = new TrieNode();
}
addWord(word: string): void {
let node = this.root;
for (const ch of word) {
if (!node.children.has(ch)) {
node.children.set(ch, new TrieNode());
}
node = node.children.get(ch)!;
}
node.isEndOfWord = true;
}
search(word: string): boolean {
return this.searchRecursive(word, 0, this.root);
}
searchRecursive(word: string, index: number, node: TrieNode): boolean {
if (index === word.length) {
return node.isEndOfWord;
}
const ch = word.charAt(index);
if (ch === '.') {
for (const child of node.children.values()) {
if (this.searchRecursive(word, index + 1, child)) {
return true;
}
}
} else {
const child = node.children.get(ch);
if (child && this.searchRecursive(word, index + 1, child)) {
return true;
}
}
return false;
}
}
复杂度分析
- 添加操作的时间复杂度:O(m),其中 m 是待添加的单词的长度。需要遍历整个单词,并在字典树中插入对应的节点。
- 搜索操作的时间复杂度:O(n),其中 n 是待搜索的字符串的长度。需要递归地遍历字典树,并判断当前字符是否匹配。
- 空间复杂度:O(26 * m * k),其中 m 是最长的待添加的单词的长度,k 是添加到字典树中的单词的数量。需要存储字典树中的所有节点以及每个节点的子节点数组。