元宝 LeetCode 3590. 第 K 小的路径异或和 Java实现
下面是LeetCode 3590「第 K 小的路径异或和」的Java 实现二进制 Trie 启发式合并。这是该题的标准正解能够在n, q ≤ 5e4下稳定通过。一、核心思路路径异或和从根0到节点u的路径上所有点权的 XOR记为xor[u]。预处理xor[u] xor[par[u]] ^ vals[u]。问题转化对每个查询(u, k)求u子树内所有xor值中第k小的不同值。数据结构选择使用二进制 Trie字典树存储子树内的所有xor值。Trie 节点维护size子树中不同数值的个数count当前节点是否是一个数值的结尾0/1支持插入自动去重查询第k小利用左右子树 size 决策启发式合并Small-to-Large后序遍历树将较小的 Trie 合并到较大的 Trie每个数值最多被插入O(log n)次复杂度时间O((n log n q) · log C)C ≈ 2^17空间O(n log C)二、关键点说明为什么不用 TreeMapTreeMap 查询第k小需要迭代O(k)最坏O(n)容易 TLE。为什么用 TrieTrie 天然支持按位比较查询第k小只需O(log C)。去重插入时若数值已存在直接跳过不增加size。三、Java 完整实现class Solution { private ListInteger[] g; // 孩子邻接表 private int[] vals; // 点权 private TrieNode[] tries; // 每个节点对应的 Trie 根 private Listint[][] qs; // 按节点分组的查询 (k, idx) private int[] ans; private static final int MAX_BIT 16; // 2^17 1e5 public int[] kthSmallest(int[] par, int[] vals, int[][] queries) { int n vals.length, q queries.length; this.vals vals; // 建树 g new ArrayList[n]; qs new ArrayList[n]; for (int i 0; i n; i) { g[i] new ArrayList(); qs[i] new ArrayList(); } for (int i 1; i n; i) g[par[i]].add(i); // 离线查询 for (int i 0; i q; i) { qs[queries[i][0]].add(new int[]{queries[i][1], i}); } tries new TrieNode[n]; ans new int[q]; dfs(0, 0); return ans; } /* ---------- DFS 启发式合并 ---------- */ private void dfs(int u, int curXor) { curXor ^ vals[u]; // 1. 找到最大的子 Trie TrieNode best null; int bestSize -1; for (int v : g[u]) { dfs(v, curXor); TrieNode t tries[v]; if (t ! null t.size bestSize) { bestSize t.size; best t; } } // 2. 以最大 Trie 为基础 TrieNode root (best ! null) ? best : new TrieNode(); // 3. 合并其他子 Trie for (int v : g[u]) { TrieNode t tries[v]; if (t best || t null) continue; merge(root, t); } // 4. 插入当前节点的 xor insert(root, curXor); tries[u] root; // 5. 回答查询 for (int[] q : qs[u]) { int k q[0]; ans[q[1]] queryKth(root, k); } } /* ---------- Trie 合并将 src 中所有值插入 dst ---------- */ private void merge(TrieNode dst, TrieNode src) { ListInteger vals new ArrayList(); collect(src, 0, vals); for (int v : vals) insert(dst, v); } private void collect(TrieNode node, int prefix, ListInteger vals) { if (node null) return; if (node.count 1) vals.add(prefix); collect(node.children[0], prefix 1, vals); collect(node.children[1], (prefix 1) | 1, vals); } /* ---------- 插入去重 ---------- */ private boolean insert(TrieNode root, int val) { TrieNode[] path new TrieNode[MAX_BIT 2]; int len 0; path[len] root; TrieNode node root; int i; for (i MAX_BIT; i 0; i--) { int b (val i) 1; if (node.children[b] null) break; node node.children[b]; path[len] node; } // 已存在 if (i 0 node.count 1) return false; // 创建缺失节点 for (; i 0; i--) { int b (val i) 1; TrieNode nn new TrieNode(); node.children[b] nn; node nn; path[len] node; } node.count 1; // 回溯更新 size for (int j len - 1; j 0; j--) { TrieNode cur path[j]; cur.size (cur.children[0] ! null ? cur.children[0].size : 0) (cur.children[1] ! null ? cur.children[1].size : 0) cur.count; } return true; } /* ---------- 查询第 k 小 ---------- */ private int queryKth(TrieNode root, int k) { if (root null || k 0 || k root.size) return -1; TrieNode cur root; int res 0; for (int i MAX_BIT; i 0; i--) { TrieNode left cur.children[0]; int leftSz (left null) ? 0 : left.size; if (k leftSz) { cur left; res 1; } else { k - leftSz; cur cur.children[1]; res (res 1) | 1; } } return res; } /* ---------- Trie 节点定义 ---------- */ static class TrieNode { TrieNode[] children new TrieNode[2]; int size; // 子树中不同数值个数 int count; // 是否为一个数值的结尾 } }四、示例演示输入par [-1,0,0,1,1,2,2] vals [1,2,3,4,5,6,7] queries [[0,1],[1,2],[2,3]]输出[0, 6, 5]五、总结✅ 使用二进制 Trie解决“第 K 小异或值”✅启发式合并控制复杂度✅ 插入去重、查询高效✅ 完全符合 LeetCode 3590 的数据范围与性能要求如果你愿意我也可以给你一个更简洁但略慢的 TreeMap 版本或者帮你逐行调试这份代码。