ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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

解题思路

这道题要求高效判断树中任意两点路径上的字符能否重排为回文串。

回文串判定条件:一个字符串能重排成回文串,当且仅当其出现奇数次的字符最多只有一个。

核心优化技巧——位掩码 + 前缀异或:

· 用26位整数表示每个字符的奇偶性:第i位为1表示字符i出现奇数次。
· 定义pref[x]为从根节点到x路径上所有字符的奇偶掩码。
· 树上两点u和v之间路径的奇偶掩码为:
mask(u→v) = pref[u] ^ pref[v] ^ (1 << char(lca(u, v)))

处理更新操作:节点字符变更时,会影响以该节点为根的整棵子树所有节点的pref值。利用DFS序(欧拉序)将子树转化为连续区间[tin[u], tout[u]],再用树状数组(Fenwick Tree)维护区间异或更新与单点查询。

---

Python3 实现代码

```python
class Solution:
def palindromePath(self, n: int, edges: list[list[int]], s: str, queries: list[str]) -> list[bool]:
# ---------- 1. 建图 ----------
g = [[] for _ in range(n)]
for u, v in edges:
g[u].append(v)
g[v].append(u)

# ---------- 2. DFS预处理:深度、父节点、DFS序、初始前缀掩码 ----------
LOG = (n).bit_length()
parent = [[-1] * n for _ in range(LOG)]
depth = [0] * n
tin = [0] * n
tout = [0] * n
pref = [0] * n # 从根到该节点的初始奇偶掩码

timer = 0
ord_a = ord('a')

def dfs(u: int, fa: int):
nonlocal timer
timer += 1
tin[u] = timer
parent[0][u] = fa
for v in g[u]:
if v == fa:
continue
depth[v] = depth[u] + 1
pref[v] = pref[u] ^ (1 << (ord(s[v]) - ord_a))
dfs(v, u)
tout[u] = timer

pref[0] = 1 << (ord(s[0]) - ord_a)
dfs(0, -1)

# ---------- 3. 二进制提升表(LCA) ----------
for k in range(1, LOG):
for i in range(n):
if parent[k-1][i] != -1:
parent[k][i] = parent[k-1][parent[k-1][i]]

def get_kth_ancestor(node: int, k: int) -> int:
for i in range(k.bit_length()):
if (k >> i) & 1:
node = parent[i][node]
if node == -1:
return -1
return node

def lca(u: int, v: int) -> int:
if depth[u] > depth[v]:
u, v = v, u
v = get_kth_ancestor(v, depth[v] - depth[u])
if u == v:
return u
for k in range(LOG - 1, -1, -1):
if parent[k][u] != parent[k][v]:
u = parent[k][u]
v = parent[k][v]
return parent[0][u]

# ---------- 4. 树状数组(区间异或更新 + 单点查询) ----------
bit = [0] * (n + 2)

def bit_add(idx: int, val: int):
while idx <= n:
bit[idx] ^= val
idx += idx & -idx

def range_xor(l: int, r: int, val: int):
bit_add(l, val)
bit_add(r + 1, val)

def point_query(idx: int) -> int:
res = 0
while idx > 0:
res ^= bit[idx]
idx -= idx & -idx
return res

# ---------- 5. 处理查询 ----------
chars = [ord(c) - ord_a for c in s]
ans = []

for q in queries:
parts = q.split()
op = parts[0]

if op == "update":
u = int(parts[1])
c = ord(parts[2]) - ord_a
if c != chars[u]:
diff = (1 << chars[u]) ^ (1 << c) # 变化的位
range_xor(tin[u], tout[u], diff)
chars[u] = c

else: # "query"
u = int(parts[1])
v = int(parts[2])
w = lca(u, v)
# 当前前缀掩码 = 初始前缀 ^ BIT累积的更新
cur_pref_u = pref[u] ^ point_query(tin[u])
cur_pref_v = pref[v] ^ point_query(tin[v])
mask = cur_pref_u ^ cur_pref_v ^ (1 << chars[w])
# 判断是否只有0个或1个1(即 mask 为0或2的幂)
ans.append((mask & (mask - 1)) == 0)

return ans
```

代码解释

模块 说明
DFS预处理 计算每个节点的depth、父节点parent[0]、DFS进入/退出时间tin/tout,以及初始前缀掩码pref。同一子树在tin和tout之间形成连续区间。
二进制提升(LCA) 构建parent[k][i]表示节点i的2^k级祖先,lca函数用于O(log n)查询最近公共祖先。
树状数组(BIT) 维护每个节点当前前缀掩码相对于初始值的增量。range_xor(tin[u], tout[u], diff)将u整棵子树所有节点的增量异或上diff。
查询处理 当前前缀掩码 = pref[u] ^ point_query(tin[u])。路径掩码公式为cur_pref_u ^ cur_pref_v ^ (1 << chars[w])。用(mask & (mask - 1)) == 0判断是否可重排为回文串。
更新处理 字符从old变为new时,diff = (1<<old) ^ (1<<new)表示变化的位,对u的子树区间做异或更新即可。

复杂度分析

· 时间复杂度:预处理O(n log n),每次查询/更新O(log n)
· 空间复杂度:O(n log n)(LCA表)+ O(n)(邻接表及其他数组)

返回列表