题目

题目来源:力扣(LeetCode)

请你设计一个数据结构,支持 添加新单词 和 查找字符串是否与任何先前添加的字符串匹配 。

实现词典类 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

思路分析

根据题意,WordDictionary 类需要支持添加单词和搜索单词的操作,可以使用字典树实现。

对于添加单词,将单词添加到字典树中即可。

对于搜索单词,从字典树的根结点开始搜索。由于待搜索的单词可能包含点号,因此在搜索过程中需要考虑点号的处理。对于当前字符是字母和点号的情况,分别按照如下方式处理:

  • 如果当前字符是字母,则判断当前字符对应的子结点是否存在,如果子结点存在则移动到子结点,继续搜索下一个字符,如果子结点不存在则说明单词不存在,返回 false;

  • 如果当前字符是点号,由于点号可以表示任何字母,因此需要对当前结点的所有非空子结点继续搜索下一个字符。

重复上述步骤,直到返回 false 或搜索完给定单词的最后一个字符。

如果搜索完给定的单词的最后一个字符,则当搜索到的最后一个结点的isEnd 为 true 时,给定的单词存在。

特别地,当搜索到点号时,只要存在一个非空子结点可以搜索到给定的单词,即返回 true。

  1. // 字典树
  2. class TrieNode {
  3. constructor() {
  4. this.children = new Array(26).fill(0);
  5. this.isEnd = false;
  6. }
  7. insert(word) {
  8. let node = this;
  9. for (let i = 0; i < word.length; i++) {
  10. const ch = word[i];
  11. // 计算当前字符的索引
  12. const index = ch.charCodeAt() - 'a'.charCodeAt();
  13. // 若索引位置还没有单词,则在此新建字典树
  14. if (node.children[index] === 0) node.children[index] = new
  15. TrieNode();
  16. node = node.children[index];
  17. }
  18. // 结尾标记单词结束了
  19. node.isEnd = true;
  20. }
  21. getChildren() {
  22. return this.children;
  23. }
  24. isEnd() {
  25. return this.isEnd;
  26. }
  27. }
  28. class WordDictionary {
  29. constructor() {
  30. this.trieRoot = new TrieNode();
  31. }
  32. addWord(word) {
  33. this.trieRoot.insert(word);
  34. }
  35. search(word) {
  36. const dfs = (index, node) => {
  37. // 若索引长度等于单词数,说明遍历完了,返回isEnd
  38. if (index === word.length) return node.isEnd;
  39. const ch = word[index];
  40. if (ch !== '.') {
  41. // 当前字符是字母,必须保证一致
  42. const child = node.children[ch.charCodeAt() - 'a'.charCodeAt()];
  43. if (child && dfs(index + 1, child)) return true;
  44. } else {
  45. // 当前字符是点,点可以代表任何字符
  46. // 只要有一个子节点是true即可
  47. for (const child of node.children) {
  48. if (child && dfs(index + 1, child)) return true;
  49. }
  50. }
  51. // 字符不存在,返回false
  52. return false;
  53. };
  54. return dfs(0, this.trieRoot);
  55. }
  56. }
  57. /**
  58. * Your WordDictionary object will be instantiated and called as such:
  59. * var obj = new WordDictionary()
  60. * obj.addWord(word)
  61. * var param_2 = obj.search(word)
  62. */