如何用 0-1 字典树(二进制 Trie)求数组中的最大异或对?
简化版
把每个数字按二进制位(从最高位到最低位)插入一棵只有 0/1 两个分支的 Trie(0-1 Trie)。求某个数能得到的最大异或值时,贪心地在每一位都尽量走”相反”的分支(当前位是 0 就找 1、是 1 就找 0,因为相异的位异或得 1,让高位尽量为 1 结果最大)。这样把「两两异或求最大」从 O(n²) 降到 O(n × 位数)。
详细版
问题:给一个数组,求任意两个数异或的最大值 max(a[i] ^ a[j])。
暴力:两两异或,O(n²)。
0-1 Trie 解法:
// 把每个数按 31~0 位插入(int 用 32 位,或按数据范围取位数)
void insert(int x) {
Node node = root;
for (int i = 31; i >= 0; i--) {
int b = (x >> i) & 1; // 取第 i 位
if (node.child[b] == null) node.child[b] = new Node();
node = node.child[b];
}
}
// 求 x 与树中已有数的最大异或
int maxXor(int x) {
Node node = root; int res = 0;
for (int i = 31; i >= 0; i--) {
int b = (x >> i) & 1;
if (node.child[b ^ 1] != null) { // 优先走相反位(异或得 1)
res |= (1 << i); // 这一位能取到 1
node = node.child[b ^ 1];
} else {
node = node.child[b]; // 只能走相同位(这一位异或是 0)
}
}
return res;
}
流程:边遍历数组边把数插入 Trie,对每个数用 maxXor 求它和已插入的数的最大异或,取全局最大。O(n × 32)。
完整版教学
一、为什么按位建 Trie
异或是按位运算:两个数异或,每一位独立,相同为 0、相异为 1。要让异或结果最大,就要让尽量高的位为 1。这自然引导我们按二进制位、从高位到低位来处理——而 Trie 正好能把「按位逐层决策」表示成一棵树:每个节点两个分支(0 和 1),从根到叶的路径就是一个数的二进制表示。
二、贪心:每一位尽量走相反的分支
求 x 与树中某数的最大异或,从最高位开始贪心:
- 异或某一位得 1,需要两个数这一位不同。
- 所以在 Trie 里,x 的当前位是
b,我们优先走b^1(相反)的分支——如果这个分支存在,说明树里有数在这一位和 x 不同,异或这一位能得 1,且因为是从高位开始,这个「1」的价值最大。 - 如果相反分支不存在,只能走相同分支(这一位异或是 0),继续看下一位。
贪心为什么正确:高位的 1 比低位所有位加起来还大(2^k > 2^k−1 + … + 1)。所以只要能让高位取 1,就一定比「高位取 0、低位全取 1」更优。逐位贪心走相反分支,得到的就是全局最大异或。
三、走一个直觉例子
树里有 2(010)、5(101),求 3(011) 的最大异或:
从高位到低位处理 3 = 0 1 1
- 最高位 0:找相反 1 → 5 那条路存在,走它,这一位异或=1
- 中间位 1:沿 5(101) 走,5 这位是 0,与 3 的 1 相反 → 异或=1
- 最低位 1:5 这位是 1,与 3 相同,只能走,异或=0
结果 3^5 = 6 (110) ✓(比 3^2=1 大)
四、复杂度
- 插入:每个数按位插入,O(位数) = O(32)。
- 查询最大异或:每个数逐位贪心,O(32)。
- 总体 O(n × 32) = O(n)(位数是常数),远优于暴力的 O(n²)。
- 空间 O(n × 32) 存 Trie 节点。
五、延伸应用
0-1 Trie 是一类「异或 / 位运算最值」问题的通用工具:
- 数组中最大异或对(本题)。
- 区间最大异或、异或和满足条件的对数(配合前缀异或和)。
- 可持久化 0-1 Trie:处理带历史版本的异或查询。
- 一些「最大/最小异或路径」的图论题也会用到。
核心套路都是「按位建 Trie + 逐位贪心」。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 建树 | 按二进制位从高到低插入每个数 |
| 贪心 | 查询时每位优先走相反 bit |
| 答案 | 逐位累加可获得的异或值 |
for bit from high downto 0:
b = (x >> bit) & 1
prefer = 1 - b
if child[prefer] exists: ans |= 1 << bit
最大异或的贪心从高位开始,因为高位的 1 比所有低位加起来都更重要。
- 误区:按十进制大小找最大数即可得到最大异或。 异或大小由二进制位差异决定,最大数不一定产生最大异或。
- 误区:从低位开始贪心也可以。 高位权重大,必须优先保证高位尽量为 1。
- 误区:0-1 Trie 只适合字符串。 它存的是数字的二进制位路径,本质仍是按字符集为 {0,1} 的 Trie。
- 追问:为什么优先走相反 bit? 相同 bit 异或为 0,相反 bit 异或为 1;想最大化当前位就走相反分支。
- 追问:复杂度是多少? 若整数按 31 或 32 位处理,插入和查询都是 O(W),总计 O(nW)。
- 追问:如何避免同一个数和自己配对? 可以先查询已插入的数再插入当前数,或在节点维护计数控制。
七、加强记忆
最大异或对用 0-1 Trie:把每个数按二进制位从高到低插入(只有 0/1 两分支)。求最大异或时逐位贪心走”相反”的分支(当前位 b 优先走 b^1,相异异或得 1,高位的 1 价值最大所以贪心正确),走不通才走相同位。把 O(n²) 暴力降到 O(n×32)。是「异或/位运算最值」问题的通用套路:按位建 Trie + 逐位贪心。