211. 添加与搜索单词 - 数据结构设计

题目描述:

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

实现词典类 WordDictionary

  • WordDictionary() 初始化词典对象
  • void addWord(word)word 添加到数据结构中,之后可以对它进行匹配
  • bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 true ;否则,返回  falseword 中可能包含一些 '.' ,每个 . 都可以表示任何一个字母。

示例:

输入:
["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 由 ’.’ 或小写英文字母组成
  • 最多调用 104addWordsearch

解题分析及思路:

方法:深度优先搜索 + 字典树

思路:

本题的核心是高效处理精确添加模糊搜索需求,适合采用 Trie(字典树) 数据结构:

  • Trie的优势:Trie通过字符分层存储,添加单词时按字符顺序构建树结构,搜索时可按字符逐层匹配,天然适配字符串的前缀/全量匹配场景。
  • 模糊搜索处理:对于通配符 '.',需遍历当前节点的所有可能子节点(26个小写字母),通过递归尝试所有路径,判断是否存在匹配的单词。

Trie节点结构定义为 WordDictionary,包含两个核心字段:

  • WordDictionaries [26]*WordDictionary:子节点数组,对应26个小写字母(索引 0-25 分别对应 a-z)。
  • IsEnd bool:标记当前节点是否为某个单词的结尾。

步骤1:初始化Trie树(Constructor)

创建根节点,初始化子节点数组为空(默认值为 nil),IsEnd 默认为 false

步骤2:添加单词(AddWord)

  • 从根节点开始,按单词的字符顺序逐层构建Trie:
    1. 取当前字符对应的索引(word[0] - 'a')。
    2. 若对应子节点不存在,则创建新节点并赋值给该索引。
    3. 递归处理剩余字符(word[1:]),直到单词长度为0。
    4. 当单词处理完毕,将当前节点的 IsEnd 标记为 true,表示此处是一个单词的结尾。

步骤3:搜索单词(Search)

  • 从根节点开始,按搜索词的字符逐层匹配:
    1. 若搜索词长度为0,返回当前节点的 IsEnd(判断是否为单词结尾)。
    2. 若当前字符是 '.':遍历所有非空的子节点,递归搜索剩余字符,若任一子节点路径匹配成功则返回 true
    3. 若当前字符是小写字母:检查对应子节点是否存在,若存在则递归搜索剩余字符;否则返回 false
// 定义Trie节点结构
type WordDictionary struct {
    WordDictionaries [26]*WordDictionary  // 子节点数组,对应a-z
    IsEnd            bool                 // 标记单词结尾
}

// 初始化Trie根节点
func Constructor() WordDictionary {
    return WordDictionary{
        WordDictionaries: [26]*WordDictionary{},  // 初始化子节点数组(默认nil)
    }
}

// 添加单词到Trie树
func (this *WordDictionary) AddWord(word string) {
    // 单词处理完毕,标记当前节点为单词结尾
    if len(word) == 0 {
        this.IsEnd = true
        return
    }
    // 计算当前字符对应的子节点索引(a-z对应0-25)
    now := word[0] - 'a'
    // 若子节点不存在,创建新节点
    if this.WordDictionaries[now] == nil {
        n := Constructor()  // 初始化新节点
        this.WordDictionaries[now] = &n
    }
    // 递归添加剩余字符
    this.WordDictionaries[now].AddWord(word[1:])
}

// 搜索单词(支持通配符'.')
func (this *WordDictionary) Search(word string) bool {
    // 单词搜索完毕,判断当前节点是否为结尾
    if len(word) == 0 {
        return this.IsEnd
    }
    // 处理通配符'.':遍历所有可能的子节点
    if word[0] == '.' {
        // 遍历26个小写字母对应的子节点
        for index := range this.WordDictionaries {
            // 若子节点存在,递归搜索剩余字符
            if v := this.WordDictionaries[index]; v != nil {
                if v.Search(word[1:]) {
                    return true  // 任一子节点匹配成功则返回true
                }
            }
        }
    } else {
        // 处理普通字符:计算索引并检查子节点
        now := word[0] - 'a'
        if v := this.WordDictionaries[now]; v != nil {
            // 子节点存在,递归搜索剩余字符
            return v.Search(word[1:])
        }
    }
    // 所有路径均不匹配
    return false
}

复杂度:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

执行结果:

  • 执行耗时:0 ms,击败了100.00 的Go用户
  • 内存消耗:2.4 MB,击败了99.83 的Go用户

通过次数 111.7K 提交次数 218.3K 通过率 51.2%

Related Posts

1026. 节点与其祖先之间的最大差值

## 题目描述:给定二叉树的根节点 root,找出存在于 不同 节点 A 和 B 之间的最大值 V,其中 V = |A.val - B.val|,且 A 是 B 的祖先。(如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先)示例 1: ![](/img/leetcode/1026节点与其祖先之间的最大差值/tmp-tre

read more

1038. 从二叉搜索树到更大和树

## 题目描述:给定一个二叉搜索树 root (BST),请将它的每个节点的值替换成树中大于或者等于该节点值的所有节点值之和。提醒一下, 二叉搜索树 满足下列约束条件:- 节点的左子树仅包含键 小于 节点键的节点。 - 节点的右子树仅包含键 大于 节点键的节点。 - 左右子树也必须是二叉搜索树。*示例 1:***![](/img/leetcode/

read more

105. 从前序与中序遍历序列构造二叉树

## 题目描述:给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1: ``` 输入: preorder = [3,9,20,15,7], inorder = [

read more

106. 从中序与后序遍历序列构造二叉树

## 题目描述:给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例 1: ``` 输入:inorder = [9,3,15,20,7], postorder

read more

109. 有序链表转换二叉搜索树

## 题目描述:给定一个单链表的头节点 head ,其中的元素 按升序排序 ,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差不超过 1。示例 1: ``` 输入: head = [-10,-3,0,5,9] 输出: [0,-3,9

read more

114. 二叉树展开为链表

## 题目描述:给你二叉树的根结点 root ,请你将它展开为一个单链表:- 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。示例 1: ``` 输入:root

read more