给你一个整数 n 和一个包含 n 个节点的无向图,节点编号从 0 到 n - 1,以及一个二维数组 ...
創建於:2025年7月13日
創建於:2025年7月13日
给你一个整数 n 和一个包含 n 个节点的无向图,节点编号从 0 到 n - 1,以及一个二维数组 edges,其中 edges[i] = [ui, vi] 表示节点 ui 和节点 vi 之间有一条边。
同时给你一个长度为 n 的字符串 label,其中 label[i] 是与节点 i 关联的字符。
你可以从任意节点开始,移动到任意相邻节点,每个节点最多 访问一次。
返回通过访问一条路径,路径中不包含重复节点,所能形成的最长回文串的长度。
回文串是指正着读和反着读相同的字符串。
示例 1:
输入: n = 3, edges = [[0,1],[1,2]], label = "aba"
输出: 3
解释:
最长的回文路径是从节点 0 到节点 2,经过节点 1,路径为 0 → 1 → 2,形成字符串 "aba"。
这是一个长度为 3 的回文串。
示例 2:
输入: n = 3, edges = [[0,1],[0,2]], label = "abc"
输出: 1
解释:
没有超过一个节点的路径可以形成回文串。
最好的选择是任意一个单独的节点,构成长度为 1 的回文串。
示例 3:
输入: n = 4, edges = [[0,2],[0,3],[3,1]], label = "bbac"
输出: 3
解释:
最长的回文路径是从节点 0 到节点 1,经过节点 3,路径为 0 → 3 → 1,形成字符串 "bcb"。
这是一个有效的回文串,长度为 3。
提示:
1 <= n <= 14
n - 1 <= edges.length <= n * (n - 1) / 2
edges[i] == [ui, vi]
0 <= ui, vi <= n - 1
ui != vi
label.length == n
label 只包含小写英文字母。
不存在重复边。
class Solution {
public int maxLen(int n, int[][] edges, String label) {
}
}
解决这个问题,通过所有例子,并且不能超时。特别注意示例2和示例3的准确性,正确答案分别是1和3,而你上次给出的结果为3和4, 与正确答案不符
下面给出一种 按“从中心同时向两端扩展”的 DFS + 位掩码记忆化 做法,能够在 n ≤ 14
的范围内轻松通过全部用例,并保证不超时。
核心思路与普通求最长回文子串的“双指针”类似,只是这里的“左右指针”放在一条无重复节点的简单路径的两个端点上,同时用一个 bitmask 记录已经走过的节点,确保路径不会重复节点。
邻接表
将无向图转为邻接表 adj
,便于遍历相邻节点。
状态设计
mask
:当前路径中已访问节点的集合(14 位以内)。l , r
:当前路径左右两端点的编号(l ≤ r
,对称化能把状态数减半)。memo[mask][l][r]
保存 从状态 (mask,l,r)
出发,再向外同时扩展可得到的最大路径长度。初始状态
(mask=1<<i , l=i , r=i)
,长度=1。(u,v)
且 label[u]==label[v]
的节点对,(mask=(1<<u)|(1<<v) , l=min(u,v) , r=max(u,v))
,长度=2。递归扩展
对当前 (mask,l,r)
l
的未访问邻接点 nl
;r
的未访问邻接点 nr
;label[nl]==label[nr]
且 nl!=nr
且 两个新点都未在 mask
中出现;(mask|(1<<nl)|(1<<nr) , min(nl,nr) , max(nl,nr))
。答案
枚举所有初始状态并调用递归函数,取返回值最大者即为所求。
C = 2^n × n × n / 2
≈ 16384 × 14 × 7
≈ 1.6 × 10⁶。deg(l) × deg(r)
对邻接点,完全图上界 n²=196
。O( n² · 2^n )
,n=14
时完全可行。O( n² · 2^n )
用来保存记忆化结果,≈ 13 MB。javaimport java.util.*; class Solution { private List<Integer>[] adj; private String label; private Map<Long, Integer> memo; // key: (mask << 8) | (l << 4) | r private int n; public int maxLen(int n, int[][] edges, String label) { this.n = n; this.label = label; adj = new ArrayList[n]; for (int i = 0; i < n; i++) adj[i] = new ArrayList<>(); for (int[] e : edges) { int u = e[0], v = e[1]; adj[u].add(v); adj[v].add(u); } memo = new HashMap<>(); int ans = 1; // 至少任选单点 /* 奇数中心:单个节点 */ for (int i = 0; i < n; i++) { ans = Math.max(ans, dfs(1 << i, i, i)); } /* 偶数中心:相邻且同字符的节点对 */ for (int[] e : edges) { int u = e[0], v = e[1]; if (label.charAt(u) == label.charAt(v)) { int mask = (1 << u) | (1 << v); int l = Math.min(u, v), r = Math.max(u, v); ans = Math.max(ans, dfs(mask, l, r)); } } return ans; } /** 记忆化 DFS:返回从当前状态能达到的最长回文路径长度 */ private int dfs(int mask, int l, int r) { long key = (((long) mask) << 8) | (l << 4) | r; // mask ≤ 14 bits, endpoints 4 bits each Integer memoVal = memo.get(key); if (memoVal != null) return memoVal; int best = Integer.bitCount(mask); // 当前路径长度 for (int nl : adj[l]) { if ((mask & (1 << nl)) != 0) continue; // 已访问 for (int nr : adj[r]) { if (nr == nl) continue; // 不能走到同一个新节点 if ((mask & (1 << nr)) != 0) continue; // 已访问 if (label.charAt(nl) != label.charAt(nr)) continue; int newMask = mask | (1 << nl) | (1 << nr); int next = dfs(newMask, Math.min(nl, nr), Math.max(nl, nr)); if (next > best) best = next; } } memo.put(key, best); return best; } }
去重技巧
(l,r)
始终按 l ≤ r
排序,避免 (l,r)
与 (r,l)
产生重复状态。nl == nr
,因为那会让两端同时走到同一节点,违反“不重复节点”的要求。示例验证
n = 3, edges = [[0,1],[1,2]], label = "aba"
→ 3n = 3, edges = [[0,1],[0,2]], label = "abc"
→ 1n = 4, edges = [[0,2],[0,3],[3,1]], label = "bbac"
→ 3与题目期望完全一致。
这样即可在 n ≤ 14
时高效求得最长回文路径长度,避免了之前示例 2/3 的错误输出。