LeetCode 211:设计添加与搜索单词的数据结构(Trie + DFS)

这道题要求设计一个 WordDictionary,既能插入单词,又能支持带通配符 . 的搜索。'.' 可以匹配任意一个字母。leetcode

核心难点在于:如何在字典树中优雅地支持通配符搜索。geeksforgeeks+1

题目概述

题目接口如下:leetcode

  • WordDictionary():初始化对象。
  • void addWord(word):把 word 加入数据结构。
  • bool search(word):如果存在任意已添加的字符串能和 word 匹配,则返回 true,否则返回 false;word 可能包含 '.',代表任意字母。

约束:单词长度不超过 25,search 中的 word 最多包含 2 个 '.',总调用次数最多 10^4。这些约束保证了基于 Trie + DFS 的解法可以轻松通过。geeksforgeeks+1

为什么选 Trie

Trie(前缀树)非常适合存储大量字符串并做前缀或精确匹配搜索。algocademy+1

  • 插入一个单词:从根开始,按字符依次向下,如果没有对应子节点就创建,最后的节点标记为"单词结尾"。geeksforgeeks
  • 普通搜索(无通配符):同样逐字符往下走,如果中途某个字符对应的子节点不存在,直接返回 false;如果能走到最后一个字符,并且当前节点是"单词结尾",返回 true。geeksforgeeks

在本题中,插入完全是标准 Trie 操作,难点都在搜索的 . 处理上。github+1

搜索逻辑:关键在 . 的处理

搜索函数的整体思路是:对字符串做 DFS 搜索。algo+1

设一个递归函数:

text 复制代码
bool dfs(trie_node *node, const char *word, int idx)

表示"当前在 node 节点,从 word[idx] 开始匹配剩余字符串,能否成功"。algo

分两种情况:

  1. 当前字符是普通字母 c

    • 在当前节点的子节点中找到字符 c 对应的 child。
    • 如果不存在,直接返回 false。
    • 如果存在,递归到这个 child,索引 idx + 1。
  2. 当前字符是 '.'

    • 无法确定下一步走哪条边,需要遍历当前节点的所有子节点。
    • 对每一个子节点 child 调用 dfs(child, word, idx + 1)。
    • 只要有一个返回 true,当前就可以返回 true;如果所有子节点都失败,返回 false。geeksforgeeks+1

当 idx 到达字符串末尾时,如果当前节点是一个单词结尾,返回 true,否则返回 false。algo

自定义 get_child_node 接口设计

你设计的接口大致是:

c 复制代码
void get_child_node(trie_node_t *parent_node,
                    trie_node_t **children,
                    int *children_num,
                    char key);

行为约定为:

  • 如果 key 不是 '.':
    • 能找到对应子节点:children 返回该 child,children_num = 1。
    • 找不到:children = NULL,children_num = 0。
  • 如果 key 是 '.':
    • children 返回"所有子节点"组成的链表头指针。
    • children_num 为子节点个数。

在 search / dfs 里就可以这样用:

  • 普通字符:get_child_node 只返回 0 或 1 个 child,直接判断是否存在并继续递归。
  • '.':get_child_node 返回一个 child 链表,上层遍历这 children_num 个 child,对每个 child 递归匹配剩余子串。

这个设计本质上就是把"根据 key 拿到可能的 child 集合"的逻辑封装到一个函数里,和主流的 Trie + DFS 解法在思想上是完全一致的,只是具体 API 风格不同。okc1.github+1

DFS 还是 BFS?

在这题里,主流解法都是 DFS,原因有:

  • 每条路径最多 25 个字符,递归深度很浅;DFS 写法简洁直观。github+1
  • '.' 的分支数有限,DFS 回溯成本可控。

理论上也可以用 BFS:队列里存 (node, idx) 状态,每次出队一个状态,根据当前字符扩展到下一层节点;但代码会比 DFS 啸嗦一些,所以一般不作为首选。accidentalfactors+1

更一般地说,Trie 自身既可以 DFS 遍历,也可以 BFS 遍历:

  • 自动补全需要"枚举某前缀下所有单词"时,大家通常用 DFS,但如果需要按长度层级排序,也可以选 BFS。ali-ibrahim137.github+1

小结

  • 数据结构:用 Trie 存所有单词。
  • 搜索策略:用 DFS 处理通配符 '.',在出现 '.' 的位置展开所有子节点分支。
  • 接口设计:把"取子节点集合"的逻辑封装成 get_child_node,对于普通字符返回 0/1 个 child,对于 '.' 返回所有 child 链表,上层统一用 DFS 处理。

从算法本质上看,这种写法与 LeetCode 官方推荐的 "Trie + DFS" 解法是一致的,足以通过所有测试用例。leetcode+2

https://leetcode.com/problems/design-add-and-search-words-data-structure/

相关推荐
叼烟扛炮10 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
leoufung11 小时前
LeetCode 149: Max Points on a Line - 解题思路详解
算法·leetcode·职场和发展
样例过了就是过了11 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
MegaDataFlowers13 小时前
206.反转链表
数据结构·链表
CN-Dust14 小时前
【C++】while语句例题专题
数据结构·c++·算法
样例过了就是过了15 小时前
LeetCode热题 不同路径
c++·算法·leetcode·动态规划
Navigator_Z16 小时前
LeetCode //C - 1031. Maximum Sum of Two Non-Overlapping Subarrays
c语言·算法·leetcode
旖-旎16 小时前
深搜练习(组合总和)(7)
c++·算法·深度优先·力扣
xieliyu.17 小时前
Java手搓数据结构:从零模拟实现无头双向非循环链表
java·数据结构·链表
如何原谅奋力过但无声18 小时前
【灵神高频面试题合集01-03】相向双指针、滑动窗口
数据结构·python·算法·leetcode