LeetCode 211 - 添加与搜索单词-数据结构设计(Design Add and Search Words Data Structure)
问题描述
请你设计一个数据结构,支持添加新单词和查找字符串是否与任何先前添加的字符串匹配。
实现词典类 WordDictionary:
WordDictionary()初始化词典对象void addWord(word)将word添加到数据结构中,之后可以对它进行匹配bool search(word)如果数据结构中存在字符串与word匹配,则返回true;否则,返回false。word中可能包含一些'.',每个'.'都可以表示任何一个字母。
示例
1 | 输入: |
约束条件
1 <= word.length <= 25addWord中的word由小写英文字母组成search中的word由'.'或小写英文字母组成- 最多调用
10^4次addWord和search
解题思路
这道题需要实现一个支持通配符搜索的字典数据结构。核心思路是使用字典树(Trie)存储单词,并用深度优先搜索(DFS)处理通配符匹配。
关键洞察
- 字典树适合前缀匹配:Trie的每个节点表示一个字符,从根到叶子的路径构成完整单词
- 通配符需要递归处理:遇到
'.'时,需要尝试所有可能的字符分支 - 状态记录很重要:每个节点需要标记是否为单词结尾
算法步骤
1. 数据结构设计
1 | TrieNode 结构: |
2. AddWord 操作
- 从根节点开始遍历待添加的单词
- 对每个字符,计算其在children数组中的索引:
index = char - 'a' - 如果对应子节点不存在,创建新节点
- 移动到子节点,继续处理下一个字符
- 单词结束时,标记当前节点的
isEnd = true
3. Search 操作(核心难点)
使用递归DFS处理两种情况:
普通字符:
- 直接沿着对应的子节点路径继续搜索
通配符 ‘.’:
- 遍历当前节点的所有非空子节点
- 对每个子节点递归调用搜索函数
- 只要有一个分支返回true,整个搜索就成功
实现细节
DFS递归函数设计
1 | func dfs(node *TrieNode, word string, index int) bool { |
算法执行示例
以搜索 ".ad" 为例(假设字典中有 “bad”, “dad”, “mad”):
1 | 1. dfs(root, ".ad", 0) |
代码实现
1 | type WordDictionary struct { |
复杂度分析
时间复杂度
AddWord操作:$O(m)$
- 其中 $m$ 是单词长度
- 需要遍历单词的每个字符
Search操作:
- 最好情况:$O(m)$ - 无通配符时
- 最坏情况:$O(26^k \cdot m)$ - 其中 $k$ 是通配符数量
- 通配符会导致指数级的搜索分支
空间复杂度
$O(ALPHABET_SIZE \times N \times M)$
- $ALPHABET_SIZE = 26$(小写字母数量)
- $N$ 是单词总数
- $M$ 是平均单词长度
- Trie的空间消耗取决于单词的公共前缀
方法比较
| 方面 | Trie + DFS | 哈希表 + 暴力搜索 |
|---|---|---|
| 时间复杂度 | AddWord: O(m) | AddWord: O(1) |
| Search: O(26^k×m) | Search: O(N×m) | |
| 空间复杂度 | O(26×N×M) | O(N×M) |
| 通配符支持 | ★★★★★ | ★★☆☆☆ |
| 实现复杂度 | ★★★☆☆ | ★★★★☆ |
| 前缀查询 | ★★★★★ | ★☆☆☆☆ |
关键收获
- Trie适合字符串前缀操作:对于涉及字符串前缀匹配的问题,Trie通常是最佳选择
- 递归处理分支逻辑:通配符问题常需要递归探索多个可能的路径
- 状态标记的重要性:
isEnd标记确保只有完整单词才能匹配成功 - 优化搜索策略:可以考虑剪枝优化,比如提前终止无效分支
常见陷阱
- 忘记检查
isEnd:搜索到单词结尾时必须验证是否为有效单词 - 边界条件处理:空节点和索引越界需要正确处理
- 通配符逻辑错误:需要遍历所有可能的子节点,而不是第一个找到的
相关问题
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.
Comments


