当前位置: 首页 > news >正文

DeepSeek LeetCode 3841. 查询树上回文路径 Java实现

解题思路

这道题的核心在于如何高效判断树中任意两点路径上的字符能否重排为回文串。

回文串的判定条件:一个字符串能重排成回文串,当且仅当其出现奇数次的字符最多只有一个。例如 "aac" 中 a 出现2次(偶),c 出现1次(奇),可重排为 "aca"。

核心优化技巧——前缀异或(Prefix XOR)与位掩码:

· 用26位整数(int)的二进制位表示每个字符的奇偶性。某位为1表示对应字符出现奇数次,0表示偶数次。
· 定义 mask[node] 为从根节点到该节点路径上所有字符的奇偶掩码。
· 树上两点 u 和 v 之间路径的奇偶掩码计算公式为:
mask(u→v) = mask(u) XOR mask(v) XOR (1 << char(lca(u, v)))
其中 lca(u, v) 是 u 和 v 的最近公共祖先。

处理更新操作:节点字符变更时,只需更新以该节点为根的整棵子树的 mask 值。利用 DFS序(欧拉序) 将子树转化为连续区间,再用 树状数组(Fenwick Tree) 维护区间异或和与单点查询。

---

Java 实现代码

```java
import java.util.*;

public class Solution {
// 链式前向星存图
private int[] head, to, nxt;
// 二进制提升(LCA)
private int[][] up;
private int[] depth;
// DFS序(欧拉序)
private int[] in, out;
private int timer;
private int maxLog;
// 树状数组
private BIT bit;
// 当前字符数组
private char[] chars;

public List<Boolean> palindromePath(int n, int[][] edges, String s, String[] queries) {
chars = s.toCharArray();
// 1. 建图
buildGraph(n, edges);

// 2. DFS预处理:深度、父节点、DFS序
maxLog = 31 - Integer.numberOfLeadingZeros(n);
up = new int[maxLog + 1][n];
depth = new int[n];
in = new int[n];
out = new int[n];
timer = 0;
dfs(0, -1);

// 3. 二进制提升表
for (int k = 1; k <= maxLog; k++) {
for (int i = 0; i < n; i++) {
up[k][i] = up[k - 1][up[k - 1][i]];
}
}

// 4. 树状数组维护每个节点的前缀掩码
bit = new BIT(n + 2);
for (int i = 0; i < n; i++) {
int mask = 1 << (chars[i] - 'a');
bit.rangeXor(in[i], out[i], mask);
}

// 5. 处理查询
List<Boolean> ans = new ArrayList<>();
for (String query : queries) {
if (query.startsWith("update")) {
// 解析:update ui c
int space1 = query.indexOf(' ');
int space2 = query.indexOf(' ', space1 + 1);
int u = Integer.parseInt(query.substring(space1 + 1, space2));
char c = query.charAt(space2 + 1);
if (c != chars[u]) {
int oldMask = 1 << (chars[u] - 'a');
int newMask = 1 << (c - 'a');
int diff = oldMask ^ newMask; // 变化的位
bit.rangeXor(in[u], out[u], diff);
chars[u] = c;
}
} else {
// 解析:query u v
int space1 = query.indexOf(' ');
int space2 = query.indexOf(' ', space1 + 1);
int u = Integer.parseInt(query.substring(space1 + 1, space2));
int v = Integer.parseInt(query.substring(space2 + 1));
int l = lca(u, v);
// 路径掩码 = mask(u) ^ mask(v) ^ char(lca)
int mask = bit.pointQuery(in[u]) ^ bit.pointQuery(in[v]) ^ (1 << (chars[l] - 'a'));
// 判断是否只有0个或1个1
ans.add((mask & (mask - 1)) == 0);
}
}
return ans;
}

// ---------- 建图 ----------
private void buildGraph(int n, int[][] edges) {
int m = edges.length;
head = new int[n];
Arrays.fill(head, -1);
to = new int[m * 2];
nxt = new int[m * 2];
for (int i = 0; i < m; i++) {
int u = edges[i][0], v = edges[i][1];
to[i * 2] = v;
nxt[i * 2] = head[u];
head[u] = i * 2;
to[i * 2 + 1] = u;
nxt[i * 2 + 1] = head[v];
head[v] = i * 2 + 1;
}
}

// ---------- DFS:深度、父节点、DFS序 ----------
private void dfs(int u, int parent) {
in[u] = ++timer;
up[0][u] = parent == -1 ? 0 : parent;
for (int e = head[u]; e != -1; e = nxt[e]) {
int v = to[e];
if (v == parent) continue;
depth[v] = depth[u] + 1;
dfs(v, u);
}
out[u] = timer;
}

// ---------- LCA:二进制提升 ----------
private int lca(int u, int v) {
if (depth[u] < depth[v]) {
int tmp = u; u = v; v = tmp;
}
// 提升u到与v同深度
int diff = depth[u] - depth[v];
for (int k = maxLog; k >= 0; k--) {
if ((diff & (1 << k)) != 0) {
u = up[k][u];
}
}
if (u == v) return u;
for (int k = maxLog; k >= 0; k--) {
if (up[k][u] != up[k][v]) {
u = up[k][u];
v = up[k][v];
}
}
return up[0][u];
}

// ---------- 树状数组(支持区间异或、单点查询) ----------
static class BIT {
int n;
int[] tree;

BIT(int n) { this.n = n; tree = new int[n + 1]; }

void add(int idx, int val) {
for (; idx <= n; idx += idx & -idx) tree[idx] ^= val;
}

// 区间 [l, r] 异或上 val
void rangeXor(int l, int r, int val) {
add(l, val);
add(r + 1, val);
}

// 单点查询
int pointQuery(int idx) {
int res = 0;
for (; idx > 0; idx -= idx & -idx) res ^= tree[idx];
return res;
}
}
}
```

代码解释

1. dfs预处理:计算每个节点的深度、父节点和 DFS 进入/退出时间戳。同一子树的节点在 in 和 out 之间形成连续区间。
2. BIT 树状数组:维护每个节点对应的前缀奇偶掩码(从根到该节点)。rangeXor(in[u], out[u], mask) 将 u 的整棵子树所有节点的前缀掩码异或上 mask。
3. 查询处理:
· 用 pointQuery(in[u]) 获取 mask(u)。
· 计算路径掩码:mask(u) ^ mask(v) ^ (1 << char(lca))。
· 判断 (mask & (mask - 1)) == 0,即二进制中是否只有0个或1个1。
4. 更新处理:字符从 old 变为 new 时,diff = (1<<old) ^ (1<<new) 表示变化的位,对 u 的子树区间异或 diff 即可。

http://www.jsqmd.com/news/1343010/

相关文章:

  • ViGEmBus虚拟手柄驱动:让Windows游戏控制器兼容性不再烦恼
  • 2026年重庆北碚区找效率高的财税公司 不妨看看辰信财税 - 奔跑123
  • 2026年8月广东驾驶式扫地车品牌**推荐:哪个好? - 工业清洁测评社
  • 郑州空气源地暖服务商哪家专业:【芬尼】管路精工 - 秋山寄远
  • 2026年唐山汽车大灯改装公司哪家好?深改车灯地址电话资料核对|营业时间与到店准备|8月7日更新 - mobible
  • 2026年哈尔滨全品类阀门选购 铸祥建材采购服务推荐 - 奔跑123
  • 郑州芬尼专卖店哪家专业:【芬尼】**认证 - 松梢月冷
  • 2026重庆不锈钢异形廊架/铜雕塑源头厂家怎么选?实用选材避坑选购指南 - mobible
  • 山海万灵 HarmonyOS 文化知识实战(18):Spring Boot 六服务的业务闭环与健康回读
  • 2026 年黄埔诚信的公路波形护栏源头厂家哪家好,路边不起眼的这玩意儿,居然是保命的最后一道防线?-江欧丝网 - 行业推荐官【认证】
  • word怎么转txt?2026年PDF转换工具实测盘点:7款在线转Word、转TXT方案横向看 - 软件小管家
  • 2026年全国盼盼暖气片推荐热门品牌**参考 - 奔跑123
  • GEO系统到底是什么?
  • 2026年8月河北改车灯车灯老化翻新服务门店推荐|唐山深改车灯总部地址、电话与到店准备|2026年8月7日资料更新 - mobible
  • 开题报告AI率检测超标怎么改?它全是论述文字,和正文不是一个改法。
  • 寄大件物流哪家便宜?2026年三大物流渠道对比,省钱攻略全解析 - 快递物流资讯
  • 大模型稀疏注意力和MoE的宽度丢失与多智能体的维度补充
  • 2026河南小型青储机怎么选?豫东青储机源头厂家靠谱选购指南:挑好关键3步不踩坑(更新时间:2026-08-07) - mobible
  • 【2026年百度暑期实习/秋招- 8月6日-算法岗-第三题-报文置换归位】(题目+思路+JavaC++Python解析+在线测试)
  • 2024年深度解析网站建设需要注意哪些核心细节以确保商业成功
  • 企业级Git分支管理策略——从Git Flow到团队协作规范
  • 2026年河北赛那改装车灯雾灯升级改装公司哪家好|电话地址整理 - mobible
  • 2026年霸州老房改造怎么选 推荐几家正规靠谱的改造公司 - 奔跑123
  • 2026郑州大型青储机怎么选?河南青饲料收获机源头生产厂家推荐选购指南(更新时间:2026-08-07) - mobible
  • 2026北京耐候户外玻璃钢雕塑/不锈钢地球雕塑源头厂家推荐:选购指南与实用避坑攻略(更新时间:2026-08-07) - mobible
  • 编程助手募起:写代码这件事正在被重写
  • 2026年深圳铜版纸吊牌源头工厂如何选避坑实用攻略 - 热点品牌推荐
  • DeepSeek LeetCode 3841. 查询树上回文路径 Python3实现
  • 亳州文武学校学费及招生详情:2026年家长实地考察武校应该关注哪些细节? - 圣龙武术朱老师
  • 2026东莞网店代运营哪家口碑好|天猫代运营收费标准,熔炼电商靠谱之选 - mobible