来源第 289 场周赛 Q4难度困难分数2126
题目描述
给你一棵 树(即一个连通、无向、无环图),根节点是节点 0 ,这棵树由编号从 0 到 n - 1 的 n 个节点组成。用下标从 0 开始、长度为 n 的数组 parent 来表示这棵树,其中 parent[i] 是节点 i 的父节点,由于节点 0 是根节点,所以 parent[0] == -1 。
另给你一个字符串 s ,长度也是 n ,其中 s[i] 表示分配给节点 i 的字符。
请你找出路径上任意一对相邻节点都没有分配到相同字符的 最长路径 ,并返回该路径的长度。
示例 1:

输入:parent = [-1,0,0,1,1,2], s = "abacbe"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:0 -> 1 -> 3 。该路径的长度是 3 ,所以返回 3 。
可以证明不存在满足上述条件且比 3 更长的路径。
示例 2:

输入:parent = [-1,0,0,0], s = "aabc"
输出:3
解释:任意一对相邻节点字符都不同的最长路径是:2 -> 0 -> 3 。该路径的长度为 3 ,所以返回 3 。
提示:
n == parent.length == s.length 1 <= n <= 105 - 对所有
i >= 1 ,0 <= parent[i] <= n - 1 均成立 parent[0] == -1 parent 表示一棵有效的树 s 仅由小写英文字母组成
解法
方法一:树形 DP
思考
在树上找一条相邻字符均不同的最长路径。 \(n \le 10^5\),不能枚举端点对。路径要么完全落在某一子树,要么在某点处把两条合法向下链拼起来。
DFS 返回「从当前点向下、且第一步字符不同」的最长链长。对每个儿子先递归;仅当 \(s[i]\neq s[j]\) 时,用当前次长链与这条新链更新全局答案,并维护最长向下链。根上再加一(计入自身)。一次遍历即可。
我们先根据数组 \(parent\) 构建邻接表 \(g\),其中 \(g[i]\) 表示节点 \(i\) 的所有子节点。
然后我们从根节点开始 DFS,对于每个节点 \(i\),我们遍历 \(g[i]\) 中的每个子节点 \(j\),如果 \(s[i] \neq s[j]\),那么我们就可以从 \(i\) 节点出发,经过 \(j\) 节点,到达某个叶子节点,这条路径的长度为 \(x = 1 + dfs(j)\),我们用 \(mx\) 记录最长的一条从节点 \(i\) 出发的路径长度。同时,在遍历的过程中,更新答案 \(ans = \max(ans, mx + x)\)。
最后,我们返回 \(ans + 1\) 即可。
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为节点个数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | class Solution:
def longestPath(self, parent: List[int], s: str) -> int:
def dfs(i: int) -> int:
mx = 0
nonlocal ans
for j in g[i]:
x = dfs(j) + 1
if s[i] != s[j]:
ans = max(ans, mx + x)
mx = max(mx, x)
return mx
g = defaultdict(list)
for i in range(1, len(parent)):
g[parent[i]].append(i)
ans = 0
dfs(0)
return ans + 1
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 | class Solution {
private List<Integer>[] g;
private String s;
private int ans;
public int longestPath(int[] parent, String s) {
int n = parent.length;
g = new List[n];
this.s = s;
Arrays.setAll(g, k -> new ArrayList<>());
for (int i = 1; i < n; ++i) {
g[parent[i]].add(i);
}
dfs(0);
return ans + 1;
}
private int dfs(int i) {
int mx = 0;
for (int j : g[i]) {
int x = dfs(j) + 1;
if (s.charAt(i) != s.charAt(j)) {
ans = Math.max(ans, mx + x);
mx = Math.max(mx, x);
}
}
return mx;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | class Solution {
public:
int longestPath(vector<int>& parent, string s) {
int n = parent.size();
vector<int> g[n];
for (int i = 1; i < n; ++i) {
g[parent[i]].push_back(i);
}
int ans = 0;
function<int(int)> dfs = [&](int i) -> int {
int mx = 0;
for (int j : g[i]) {
int x = dfs(j) + 1;
if (s[i] != s[j]) {
ans = max(ans, mx + x);
mx = max(mx, x);
}
}
return mx;
};
dfs(0);
return ans + 1;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | func longestPath(parent []int, s string) int {
n := len(parent)
g := make([][]int, n)
for i := 1; i < n; i++ {
g[parent[i]] = append(g[parent[i]], i)
}
ans := 0
var dfs func(int) int
dfs = func(i int) int {
mx := 0
for _, j := range g[i] {
x := dfs(j) + 1
if s[i] != s[j] {
ans = max(ans, x+mx)
mx = max(mx, x)
}
}
return mx
}
dfs(0)
return ans + 1
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | function longestPath(parent: number[], s: string): number {
const n = parent.length;
const g: number[][] = Array.from({ length: n }, () => []);
for (let i = 1; i < n; ++i) {
g[parent[i]].push(i);
}
let ans = 0;
const dfs = (i: number): number => {
let mx = 0;
for (const j of g[i]) {
const x = dfs(j) + 1;
if (s[i] !== s[j]) {
ans = Math.max(ans, mx + x);
mx = Math.max(mx, x);
}
}
return mx;
};
dfs(0);
return ans + 1;
}
|
方法二:树形 DP + 显式栈
思考
在树上找一条相邻字符均不同的最长路径。 \(n \le 10^5\),从根递归求每棵子树的向下链,链上的调用深度就是 \(n\),会超出递归栈。
这样的路径要么完全落在某一子树,要么在某个点把两条向下链拼起来。离开一个节点时,儿子的向下链已经知道:仅当 \(s[i] \neq s[j]\) 时,这条链才能接到 \(i\) 上,长度是儿子的链长加 \(1\)。用当前最长的一条与新链更新全局答案,并留下更长的那条作为本节点的向下链。
显式栈按 \((节点, 状态)\) 做后序。进入时先压出栈标记再压孩子,离开时按上面的规则更新答案和向下链。最后把答案加 \(1\),补上路径经过的节点数。
我们先根据数组 \(parent\) 构建邻接表 \(g\),其中 \(g[i]\) 表示节点 \(i\) 的所有子节点。
用显式栈从根做后序。离开节点 \(i\) 时,遍历每个子节点 \(j\),记 \(x\) 为从 \(j\) 向下的最长链再加 \(1\)。若 \(s[i] \neq s[j]\),就用已经记下的最长向下链 \(mx\) 与 \(x\) 更新答案 \(ans = \max(ans, mx + x)\),再把 \(mx\) 更新为 \(\max(mx, x)\)。 \(mx\) 是从 \(i\) 向下、且第一步字符不同的最长链。
最后,我们返回 \(ans + 1\) 即可。
时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)。其中 \(n\) 为节点个数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | class Solution:
def longestPath(self, parent: List[int], s: str) -> int:
n = len(parent)
g = [[] for _ in range(n)]
for i in range(1, n):
g[parent[i]].append(i)
down = [0] * n
ans = 0
stk = [(0, 0)]
while stk:
i, state = stk.pop()
if state == 0:
stk.append((i, 1))
for j in g[i]:
stk.append((j, 0))
else:
mx = 0
for j in g[i]:
x = down[j] + 1
if s[i] != s[j]:
ans = max(ans, mx + x)
mx = max(mx, x)
down[i] = mx
return ans + 1
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | class Solution {
public int longestPath(int[] parent, String s) {
int n = parent.length;
List<Integer>[] g = new List[n];
Arrays.setAll(g, k -> new ArrayList<>());
for (int i = 1; i < n; ++i) {
g[parent[i]].add(i);
}
int[] down = new int[n];
int ans = 0;
Deque<int[]> stk = new ArrayDeque<>();
stk.push(new int[] {0, 0});
while (!stk.isEmpty()) {
int[] cur = stk.pop();
int i = cur[0], state = cur[1];
if (state == 0) {
stk.push(new int[] {i, 1});
for (int j : g[i]) {
stk.push(new int[] {j, 0});
}
} else {
int mx = 0;
for (int j : g[i]) {
int x = down[j] + 1;
if (s.charAt(i) != s.charAt(j)) {
ans = Math.max(ans, mx + x);
mx = Math.max(mx, x);
}
}
down[i] = mx;
}
}
return ans + 1;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 | class Solution {
public:
int longestPath(vector<int>& parent, string s) {
int n = parent.size();
vector<vector<int>> g(n);
for (int i = 1; i < n; ++i) {
g[parent[i]].push_back(i);
}
vector<int> down(n);
int ans = 0;
vector<array<int, 2>> stk{{0, 0}};
while (!stk.empty()) {
auto [i, state] = stk.back();
stk.pop_back();
if (state == 0) {
stk.push_back({i, 1});
for (int j : g[i]) {
stk.push_back({j, 0});
}
} else {
int mx = 0;
for (int j : g[i]) {
int x = down[j] + 1;
if (s[i] != s[j]) {
ans = max(ans, mx + x);
mx = max(mx, x);
}
}
down[i] = mx;
}
}
return ans + 1;
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | func longestPath(parent []int, s string) int {
n := len(parent)
g := make([][]int, n)
for i := 1; i < n; i++ {
g[parent[i]] = append(g[parent[i]], i)
}
down := make([]int, n)
ans := 0
stk := [][2]int{{0, 0}}
for len(stk) > 0 {
cur := stk[len(stk)-1]
stk = stk[:len(stk)-1]
i, state := cur[0], cur[1]
if state == 0 {
stk = append(stk, [2]int{i, 1})
for _, j := range g[i] {
stk = append(stk, [2]int{j, 0})
}
} else {
mx := 0
for _, j := range g[i] {
x := down[j] + 1
if s[i] != s[j] {
ans = max(ans, x+mx)
mx = max(mx, x)
}
}
down[i] = mx
}
}
return ans + 1
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | function longestPath(parent: number[], s: string): number {
const n = parent.length;
const g: number[][] = Array.from({ length: n }, () => []);
for (let i = 1; i < n; ++i) {
g[parent[i]].push(i);
}
const down = Array(n).fill(0);
let ans = 0;
const stk: [number, number][] = [[0, 0]];
while (stk.length) {
const [i, state] = stk.pop()!;
if (state === 0) {
stk.push([i, 1]);
for (const j of g[i]) {
stk.push([j, 0]);
}
} else {
let mx = 0;
for (const j of g[i]) {
const x = down[j] + 1;
if (s[i] !== s[j]) {
ans = Math.max(ans, mx + x);
mx = Math.max(mx, x);
}
}
down[i] = mx;
}
}
return ans + 1;
}
|