insert()需设is_end=true,search()须检查is_end,startswith()只需路径存在;空字符串合法;字符集决定用array(小写英文)或unordered_map(其他情况)。

insert() 和 search() 为什么不能共用“走完路径”逻辑
因为 search() 要求终点节点必须是单词结尾,而 startsWith() 只需路径存在。如果把两者都写成“走到头就返回 true”,search("app") 在只插入了 "apple" 的情况下也会返回 true——这明显错了。
-
insert()必须在循环结束后显式设node->is_end = true;漏掉这行,所有search()都会失败 -
search()必须检查node->is_end,不能只看路径是否存在 -
startsWith()才是走完就返回true,不查is_end - 空字符串
""是合法单词,插入时要允许root->is_end = true
用 std::array<:shared_ptr>, 26></:shared_ptr> 还是 std::unordered_map<char std::shared_ptr>></char>
取决于字符集和性能敏感点:纯小写英文(a–z)用数组快且省内存;只要出现大写、数字、中文或不确定输入,就必须切到 unordered_map,否则 c - 'a' 会越界或索引错乱。
- 数组方案:索引计算为
c - 'a',要求调用方保证c >= 'a' && c ,否则行为未定义 -
unordered_map方案:支持任意char值,但每次查找有哈希开销,且内存占用略高 - 若需字典序补全(如自动提示按字母排),
unordered_map不保证遍历顺序,得换std::map或 DFS 前手动排序 key - 高频插入/查询场景下,数组的 cache 局部性更好,实测快 15%–30%
前缀补全怎么高效返回前 10 个结果
不能等 DFS 完全跑完再排序取前 10,尤其当子树极深时——得边搜边剪枝,靠提前计数或限制深度。
- 在每个节点缓存
word_count(以该节点为根的子树中完整单词总数),能快速判断是否值得继续 DFS - DFS 过程中用引用传当前字符串(
const std::string& prefix),避免每层构造临时std::string拖慢响应 - 结果容器建议用
std::vector<:string></:string>,收集满 10 个就直接return,别全量枚举 - 如果要求严格字典序,且用的是
unordered_map,得先提取所有 key,std::sort后再遍历子节点
内存管理最容易被忽略的坑
裸指针方案(TrieNode*)看似轻量,但 delete 极易遗漏或重复;而 std::shared_ptr 若设计不当,会在父子节点间形成循环引用,导致内存泄漏。
立即学习“C++免费学习笔记(深入)”;
- 正确做法:子节点用
std::shared_ptr,父节点**不存反向指针**(即 TrieNode 里不要存 parent) - 避免在
TrieNode中持有std::shared_ptr<trienode></trienode>类型的 parent 成员 - 若用裸指针,务必配对实现
clear()递归释放,且禁止拷贝构造(或显式删除) - 静态分配数组(如
trie[MAXM][26])虽无内存管理负担,但容量固定,超限后行为不可控,上线前必须做cnt边界检查
实际写的时候,最常出问题的不是算法逻辑,而是 is_end 设没设、shared_ptr 循环引用、以及把 search 当成 startsWith 用。这几个点卡住,调试半天也看不出哪错了。










