算法

字典树(Trie)

O(L) 前缀查询——存储字符串最高效的树形结构

Trie 概览

Trie(前缀树)将字符串逐字符存入树中,共享公共前缀。单次插入/查找时间 O(L),L 为字符串长度,与字典大小无关。

操作复杂度

操作时间说明
insert(word)O(L)L = word 长度,逐字符插入
search(word)O(L)逐字符匹配,检查 isEnd
startsWith(prefix)O(L)逐字符匹配,不检查 isEnd
空间O(总字符数)最坏每个字符独占一个节点

Trie vs HashMap

场景推荐
精确查找(key 存在?)HashMap(O(1))
前缀查找(startsWith)Trie
自动补全、最长公共前缀Trie
按字典序遍历所有 keyTrie(DFS 中序)

基础 Trie

节点含两个字段:children[26](或 unordered_map)和 isEnd 标记。insert() 沿路创建缺失节点;search() 沿路前进,结尾检查 isEnd;startsWith() 同 search() 但不检查 isEnd。

struct TrieNode {
    TrieNode* children[26] = {};
    bool isEnd = false;
};

class Trie {
    TrieNode* root = new TrieNode();
public:
    void insert(const string& word) {
        TrieNode* cur = root;
        for (char c : word) {
            int idx = c - 'a';
            if (!cur->children[idx])
                cur->children[idx] = new TrieNode();
            cur = cur->children[idx];
        }
        cur->isEnd = true;
    }

    bool search(const string& word) {
        TrieNode* cur = root;
        for (char c : word) {
            int idx = c - 'a';
            if (!cur->children[idx]) return false;
            cur = cur->children[idx];
        }
        return cur->isEnd;
    }

    bool startsWith(const string& prefix) {
        TrieNode* cur = root;
        for (char c : prefix) {
            int idx = c - 'a';
            if (!cur->children[idx]) return false;
            cur = cur->children[idx];
        }
        return true;
    }
};

典型题目

  • 208. 实现 Trie(前缀树)
  • 1268. 搜索推荐系统(前缀匹配 + 排序)
  • 820. 单词的压缩编码(后缀字典树)

children 的选择仅小写字母用 children[26] 固定数组,空间浪费但速度快;字符集大时改用 unordered_map'<'char, TrieNode*'>'。

练习:Trie — 基础操作代码练习
测试用例(2 个)
用例 1
输入:7 I apple I app S apple S app S ap P app P xyz
期望:1 1 0 1 0
用例 2
输入:4 I hello I world S hell P hell
期望:0 1

Trie + DFS:单词搜索

212. 单词搜索 II:将所有单词插入 Trie,然后 DFS 遍历网格。在 Trie 中前进:若当前节点的 isEnd 为 true 则记录单词;若子节点不存在则剪枝。

优势

  • 同时搜索多个单词,比对每个单词分别 DFS 快 O(单词数) 倍
  • 剪枝:Trie 中无匹配前缀时立即停止,避免无效 DFS 分支
  • 找到单词后可从 Trie 删除(isEnd = false),避免重复添加

复杂度DFS O(M × N × 4^L) 最坏,但 Trie 剪枝可大幅降低实际运行时间,L 为最长单词长度。

练习:Trie — 前缀匹配计数代码练习
测试用例(2 个)
用例 1
输入:3 3 apple application banana app ban xyz
期望:2
用例 2
输入:2 2 hello world hell xyz
期望:1

XOR Trie:最大异或

XOR Trie 将整数按位(从高位到低位)插入二叉 Trie。查询最大 XOR 时贪心选取与当前位相反的分支(若存在),使 XOR 尽可能大。单次操作 O(32) 或 O(31)。

// XOR Trie — find max XOR of any two numbers in array
struct XorTrie {
    int ch[2];
    XorTrie() { ch[0] = ch[1] = 0; }
};

vector<XorTrie> trie(1);

void insert(int x) {
    int cur = 0;
    for (int bit = 30; bit >= 0; bit--) {
        int b = (x >> bit) & 1;
        if (!trie[cur].ch[b]) {
            trie.emplace_back();
            trie[cur].ch[b] = trie.size() - 1;
        }
        cur = trie[cur].ch[b];
    }
}

int query(int x) {
    int cur = 0, res = 0;
    for (int bit = 30; bit >= 0; bit--) {
        int b = (x >> bit) & 1;
        int want = 1 - b; // try the opposite bit
        if (trie[cur].ch[want]) { res |= (1 << bit); cur = trie[cur].ch[want]; }
        else cur = trie[cur].ch[b];
    }
    return res;
}

int findMaximumXOR(vector<int>& nums) {
    trie.assign(1, XorTrie());
    int ans = 0;
    for (int x : nums) {
        insert(x);
        ans = max(ans, query(x));
    }
    return ans;
}

典型题目

  • 421. 数组中两个数的最大异或值
  • 1803. 统计异或值在范围内的数对有多少

位顺序从最高有效位(MSB,通常是第 30 或 31 位)向低位插入,保证贪心选择时高位优先(贡献值更大)。

练习:Trie — XOR Trie 最大异或代码练习
测试用例(3 个)
用例 1
输入:4 3 10 5 25
期望:28
用例 2
输入:2 14 70
期望:84
用例 3
输入:1 0
期望:0