字典树(Trie)算法题的技巧总结

15 阅读5分钟

一、解题思维总结

1. 何时使用字典树(Trie)?

场景解法
字符串集合的快速插入和查找使用 Trie 数据结构,时间复杂度 O(m),m 为字符串长度
前缀匹配问题(自动补全、拼写检查)Trie 的 startsWith 操作,高效判断前缀是否存在
带通配符的字符串搜索Trie + DFS 递归搜索,处理通配符的多分支情况
字符串集合的存储优化Trie 共享公共前缀,节省存储空间

2. 复杂度分析

  • 插入操作时间复杂度:O(m),m 为字符串长度
  • 搜索操作时间复杂度:O(m)
  • 前缀查找时间复杂度:O(m)
  • 空间复杂度:O(N×M),N 为字符串数量,M 为平均长度(共享前缀时实际更小)

3. 常用技巧

  1. 节点结构:使用对象嵌套 node[char] = {},每个节点代表一个字符
  2. 结束标志:用 isEndisWord 属性标记单词结束
  3. 字符遍历:使用 for...of 遍历字符串的每个字符
  4. 节点跳转node = node[char] 移动到子节点继续处理
  5. DFS 搜索:递归遍历所有子节点,使用 index 参数避免字符串切片

二、核心技术

技巧一:Trie 基本结构实现

适用场景:需要高效存储和检索字符串集合,支持插入、精确搜索、前缀查找

核心要点

  • 每个节点是一个对象,键为字符,值为子节点对象
  • 从根节点到任意节点的路径组成一个字符串前缀
  • isEnd 标志位标记单词结束,区分 "app" 和 "apple"
  • 初始化时创建空根节点 this.root = {}

典型例题实现 Trie (前缀树)

var Trie = function() {
    this.root = {};
};

Trie.prototype.insert = function(word) {
    let node = this.root;
    for (let char of word) {
        if (!node[char]) {
            node[char] = {};
        }
        node = node[char];
    }
    node.isEnd = true;
};

Trie.prototype.search = function(word) {
    let node = this.root;
    for (let char of word) {
        if (!node[char]) {
            return false;
        }
        node = node[char];
    }
    return node.isEnd === true;
};

Trie.prototype.startsWith = function(prefix) {
    let node = this.root;
    for (let char of prefix) {
        if (!node[char]) {
            return false;
        }
        node = node[char];
    }
    return true;
};

技巧二:带通配符的 DFS 搜索

适用场景:字符串搜索支持通配符(如 '.' 匹配任意字符),需要遍历所有可能的分支

核心要点

  • 通配符时遍历当前节点的所有子节点,递归搜索
  • 使用 index 参数而非字符串切片,避免额外空间开销
  • DFS 函数签名:dfs(node, word, index)
  • 跳过 isWord 等标志属性,只处理字符键
  • 任意分支返回 true 即整体返回 true(短路优化)

典型例题添加与搜索单词 - 数据结构设计

var WordDictionary = function() {
    this.root = {};
};

WordDictionary.prototype.addWord = function(word) {
    let node = this.root;
    for (let char of word) {
        if (!node[char]) {
            node[char] = {};
        }
        node = node[char];
    }
    node.isWord = true;
};

WordDictionary.prototype.search = function(word) {
    return this.dfs(this.root, word, 0);
};

WordDictionary.prototype.dfs = function(node, word, index) {
    if (index === word.length) {
        return node.isWord === true;
    }
    
    const char = word[index];
    
    if (char === '.') {
        for (let childChar in node) {
            if (childChar === 'isWord') {
                continue;
            }
            if (this.dfs(node[childChar], word, index + 1)) {
                return true;
            }
        }
    } else {
        if (node[char] && this.dfs(node[char], word, index + 1)) {
            return true;
        }
    }
    
    return false;
};

技巧三:节点创建与跳转模式

适用场景:Trie 的插入和搜索操作中,需要创建新节点或移动到已存在节点

核心要点

  • 插入时:不存在则创建 if (!node[char]) node[char] = {}
  • 搜索时:不存在则返回 false if (!node[char]) return false
  • 统一跳转:node = node[char]
  • 先检查后跳转,避免访问 undefined

模板代码

// 插入模式
let node = this.root;
for (let char of word) {
    if (!node[char]) {
        node[char] = {};  // 不存在则创建
    }
    node = node[char];    // 跳转到子节点
}
node.isEnd = true;        // 标记结束

// 搜索模式
let node = this.root;
for (let char of word) {
    if (!node[char]) {
        return false;     // 不存在则失败
    }
    node = node[char];    // 跳转到子节点
}
return node.isEnd === true;  // 检查结束标志

三、易错点提醒

  1. 不要用数组简单存储:虽然可以通过测试,但失去了 Trie 的核心优势(前缀共享、快速前缀查找),时间复杂度退化为 O(n×m)

  2. isEnd/isWord 的判断:节点没有 isEnd 属性时返回 undefined,需要用 === true!! 转换为布尔值

  3. DFS 遍历子节点时跳过标志属性:使用 for...in 遍历会包含 isWord 等属性,需要 if (childChar === 'isWord') continue 跳过

  4. 前缀查找 vs 精确搜索:startsWith 只需遍历完前缀即可返回 true,search 需要额外检查 isEnd 标志

  5. 通配符搜索的终止条件:index === word.length 时,必须检查 isWord 而非直接返回 true

四、学习心得

Trie 数据结构的优势

  1. 前缀共享:多个字符串共享公共前缀节点,节省存储空间
  2. 快速查找:查找时间与字符串长度相关,与集合大小无关
  3. 前缀操作:天然支持前缀查找,适合自动补全、拼写检查等场景
  4. 动态扩展:可以随时插入新字符串,无需重建整个数据结构

解题思维模式

  1. 从简单到高效:先用数组等简单结构理解问题,再优化为 Trie 结构
  2. 对象嵌套思维:将树形结构转化为对象的嵌套,每个对象是一个节点
  3. 标志位设计:用特殊属性(isEnd/isWord)标记节点状态,区分路径和完整单词
  4. 递归与迭代:基本操作用迭代,复杂搜索(如通配符)用递归 DFS
  5. 短路优化:DFS 中一旦找到匹配立即返回,避免不必要的搜索

五、使用场景总结

字典树的使用场景包括:字符串集合的高效存储与检索、前缀匹配与自动补全、拼写检查与单词验证、带通配符的字符串搜索、IP 路由表的最长前缀匹配、关键词过滤与敏感词检测。