请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。
实现词典类 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
方法一:字典树
预备知识
字典树(前缀树)是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。前缀树可以用 $O(|S|)$ 的时间复杂度完成如下操作,其中 $|S|$ 是插入字符串或查询前缀的长度:
字典树的实现可以参考「208. 实现 Trie (前缀树) 的官方题解 」。
思路和算法
根据题意,$\texttt{WordDictionary}$ 类需要支持添加单词和搜索单词的操作,可以使用字典树实现。
对于添加单词,将单词添加到字典树中即可。
对于搜索单词,从字典树的根结点开始搜索。由于待搜索的单词可能包含点号,因此在搜索过程中需要考虑点号的处理。对于当前字符是字母和点号的情况,分别按照如下方式处理:
重复上述步骤,直到返回 $\text{false}$ 或搜索完给定单词的最后一个字符。
如果搜索完给定的单词的最后一个字符,则当搜索到的最后一个结点的 $\textit{isEnd}$ 为 $\text{true}$ 时,给定的单词存在。
特别地,当搜索到点号时,只要存在一个非空子结点可以搜索到给定的单词,即返回 $\text{true}$。
代码
[sol1-Java]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| class WordDictionary { private Trie root;
public WordDictionary() { root = new Trie(); } public void addWord(String word) { root.insert(word); } public boolean search(String word) { return dfs(word, 0, root); }
private boolean dfs(String word, int index, Trie node) { if (index == word.length()) { return node.isEnd(); } char ch = word.charAt(index); if (Character.isLetter(ch)) { int childIndex = ch - 'a'; Trie child = node.getChildren()[childIndex]; if (child != null && dfs(word, index + 1, child)) { return true; } } else { for (int i = 0; i < 26; i++) { Trie child = node.getChildren()[i]; if (child != null && dfs(word, index + 1, child)) { return true; } } } return false; } }
class Trie { private Trie[] children; private boolean isEnd;
public Trie() { children = new Trie[26]; isEnd = false; } public void insert(String word) { Trie node = this; for (int i = 0; i < word.length(); i++) { char ch = word.charAt(i); int index = ch - 'a'; if (node.children[index] == null) { node.children[index] = new Trie(); } node = node.children[index]; } node.isEnd = true; }
public Trie[] getChildren() { return children; }
public boolean isEnd() { return isEnd; } }
|
[sol1-C#]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| public class WordDictionary { private Trie root;
public WordDictionary() { root = new Trie(); } public void AddWord(string word) { root.Insert(word); } public bool Search(string word) { return DFS(word, 0, root); }
private bool DFS(String word, int index, Trie node) { if (index == word.Length) { return node.IsEnd; } char ch = word[index]; if (char.IsLetter(ch)) { int childIndex = ch - 'a'; Trie child = node.Children[childIndex]; if (child != null && DFS(word, index + 1, child)) { return true; } } else { for (int i = 0; i < 26; i++) { Trie child = node.Children[i]; if (child != null && DFS(word, index + 1, child)) { return true; } } } return false; } }
class Trie { public Trie[] Children { get; } public bool IsEnd { get; set; }
public Trie() { Children = new Trie[26]; IsEnd = false; } public void Insert(String word) { Trie node = this; for (int i = 0; i < word.Length; i++) { char ch = word[i]; int index = ch - 'a'; if (node.Children[index] == null) { node.Children[index] = new Trie(); } node = node.Children[index]; } node.IsEnd = true; } }
|
[sol1-C++]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| struct TrieNode{ vector<TrieNode *> child; bool isEnd; TrieNode() { this->child = vector<TrieNode *>(26,nullptr); this->isEnd = false; } };
void insert(TrieNode * root, const string & word) { TrieNode * node = root; for (auto c : word) { if (node->child[c - 'a'] == nullptr) { node->child[c - 'a'] = new TrieNode(); } node = node->child[c - 'a']; } node->isEnd = true; }
class WordDictionary { public: WordDictionary() { trie = new TrieNode(); } void addWord(string word) { insert(trie,word); } bool search(string word) { return dfs(word, 0, trie); }
bool dfs(const string & word,int index,TrieNode * node) { if (index == word.size()) { return node->isEnd; } char ch = word[index]; if (ch >= 'a' && ch <= 'z') { TrieNode * child = node->child[ch - 'a']; if (child != nullptr && dfs(word, index + 1, child)) { return true; } } else if (ch == '.') { for (int i = 0; i < 26; i++) { TrieNode * child = node->child[i]; if (child != nullptr && dfs(word, index + 1, child)) { return true; } } } return false; } private: TrieNode * trie; };
|
[sol1-Golang]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| type TrieNode struct { children [26]*TrieNode isEnd bool }
func (t *TrieNode) Insert(word string) { node := t for _, ch := range word { ch -= 'a' if node.children[ch] == nil { node.children[ch] = &TrieNode{} } node = node.children[ch] } node.isEnd = true }
type WordDictionary struct { trieRoot *TrieNode }
func Constructor() WordDictionary { return WordDictionary{&TrieNode{}} }
func (d *WordDictionary) AddWord(word string) { d.trieRoot.Insert(word) }
func (d *WordDictionary) Search(word string) bool { var dfs func(int, *TrieNode) bool dfs = func(index int, node *TrieNode) bool { if index == len(word) { return node.isEnd } ch := word[index] if ch != '.' { child := node.children[ch-'a'] if child != nil && dfs(index+1, child) { return true } } else { for i := range node.children { child := node.children[i] if child != nil && dfs(index+1, child) { return true } } } return false } return dfs(0, d.trieRoot) }
|
[sol1-Python3]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| class TrieNode: def __init__(self): self.children = [None] * 26 self.isEnd = False
def insert(self, word: str) -> None: node = self for ch in word: ch = ord(ch) - ord('a') if not node.children[ch]: node.children[ch] = TrieNode() node = node.children[ch] node.isEnd = True
class WordDictionary: def __init__(self): self.trieRoot = TrieNode()
def addWord(self, word: str) -> None: self.trieRoot.insert(word)
def search(self, word: str) -> bool: def dfs(index: int, node: TrieNode) -> bool: if index == len(word): return node.isEnd ch = word[index] if ch != '.': child = node.children[ord(ch) - ord('a')] if child is not None and dfs(index + 1, child): return True else: for child in node.children: if child is not None and dfs(index + 1, child): return True return False
return dfs(0, self.trieRoot)
|
[sol1-JavaScript]1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| var WordDictionary = function() { this.trieRoot = new TrieNode(); };
WordDictionary.prototype.addWord = function(word) { this.trieRoot.insert(word); };
WordDictionary.prototype.search = function(word) { const dfs = (index, node) => { if (index === word.length) { return node.isEnd; } const ch = word[index]; if (ch !== '.') { const child = node.children[ch.charCodeAt() - 'a'.charCodeAt()] if (child && dfs(index + 1, child)) { return true; } } else { for (const child of node.children) { if (child && dfs(index + 1, child)) { return true; } } } return false; } return dfs(0, this.trieRoot); };
class TrieNode { constructor() { this.children = new Array(26).fill(0); this.isEnd = false; }
insert(word) { let node = this; for (let i = 0; i < word.length; i++) { const ch = word[i]; const index = ch.charCodeAt() - 'a'.charCodeAt(); if (node.children[index] === 0) { node.children[index] = new TrieNode(); } node = node.children[index]; } node.isEnd = true; }
getChildren() { return this.children; }
isEnd() { return this.isEnd; } }
|
复杂度分析
时间复杂度:初始化为 $O(1)$,添加单词为 $O(|S|)$,搜索单词为 $O(|\Sigma|^{|S|})$,其中 $|S|$ 是每次添加或搜索的单词的长度,$\Sigma$ 是字符集,这道题中的字符集为全部小写英语字母,$|\Sigma| = 26$。
最坏情况下,待搜索的单词中的每个字符都是点号,则每个字符都有 $|\Sigma|$ 种可能。
空间复杂度:$O(|T|\cdot|\Sigma|)$,其中 $|T|$ 是所有添加的单词的长度之和,$\Sigma$ 是字符集,这道题中的字符集为全部小写英语字母,$|\Sigma| = 26$。