
题目描述
Alice 有一棵 n
个节点的树,节点编号为 0
到 n - 1
。树用一个长度为 n - 1
的二维整数数组 edges
表示,其中 edges[i] = [ai, bi]
,表示树中节点 ai
和 bi
之间有一条边。
Alice 想要 Bob 找到这棵树的根。她允许 Bob 对这棵树进行若干次 猜测 。每一次猜测,Bob 做如下事情:
- 选择两个 不相等 的整数
u
和 v
,且树中必须存在边 [u, v]
。
- Bob 猜测树中
u
是 v
的 父节点 。
Bob 的猜测用二维整数数组 guesses
表示,其中 guesses[j] = [uj, vj]
表示 Bob 猜 uj
是 vj
的父节点。
Alice 非常懒,她不想逐个回答 Bob 的猜测,只告诉 Bob 这些猜测里面 至少 有 k
个猜测的结果为 true
。
给你二维整数数组 edges
,Bob 的所有猜测和整数 k
,请你返回可能成为树根的 节点数目 。如果没有这样的树,则返回 0
。
示例 1:

输入:edges = [[0,1],[1,2],[1,3],[4,2]], guesses = [[1,3],[0,1],[1,0],[2,4]], k = 3
输出:3
解释:
根为节点 0 ,正确的猜测为 [1,3], [0,1], [2,4]
根为节点 1 ,正确的猜测为 [1,3], [1,0], [2,4]
根为节点 2 ,正确的猜测为 [1,3], [1,0], [2,4]
根为节点 3 ,正确的猜测为 [1,0], [2,4]
根为节点 4 ,正确的猜测为 [1,3], [1,0]
节点 0 ,1 或 2 为根时,可以得到 3 个正确的猜测。
示例 2:

输入:edges = [[0,1],[1,2],[2,3],[3,4]], guesses = [[1,0],[3,4],[2,1],[3,2]], k = 1
输出:5
解释:
根为节点 0 ,正确的猜测为 [3,4]
根为节点 1 ,正确的猜测为 [1,0], [3,4]
根为节点 2 ,正确的猜测为 [1,0], [2,1], [3,4]
根为节点 3 ,正确的猜测为 [1,0], [2,1], [3,2], [3,4]
根为节点 4 ,正确的猜测为 [1,0], [2,1], [3,2]
任何节点为根,都至少有 1 个正确的猜测。
提示:
edges.length == n - 1
2 <= n <= 105
1 <= guesses.length <= 105
0 <= ai, bi, uj, vj <= n - 1
ai != bi
uj != vj
edges
表示一棵有效的树。
guesses[j]
是树中的一条边。
guesses
是唯一的。
0 <= k <= guesses.length
解法
方法一:树形 DP(换根)
我们先遍历题目给定的边集合 \(edges\),将其转换为邻接表 \(g\),其中 \(g[i]\) 表示节点 \(i\) 的所有邻接节点。用哈希表 \(gs\) 记录题目给定的猜测集合 \(guesses\)。
接下来,我们先从节点 \(0\) 开始,进行一次 DFS,统计从节点 \(0\) 出发,能够到达的所有节点中,有多少条边在 \(guesses\) 中。我们用变量 \(cnt\) 记录这个数量。
然后,我们再从节点 \(0\) 开始,进行一次 DFS,统计以每个点为根的树中,有多少条边在 \(guesses\) 中。如果这个数量大于等于 \(k\),则说明该节点是一个可能的根节点,我们将答案加 \(1\)。
因此,问题就转化为了求以每个点为根的树中,有多少条边在 \(guesses\) 中。我们已经知道了从节点 \(0\) 出发,能够到达的所有节点中,有多少条边在 \(guesses\) 中,即 \(cnt\)。我们可以通过在 DFS 中,将 \(cnt\) 的值加上或减去当前边是否在 \(guesses\) 中,来维护这个值。
假设我们当前遍历到节点 \(i\),此时 \(cnt\) 表示以 \(i\) 为根节点,有多少条边在 \(guesses\) 中。那么对于 \(i\) 的每个邻接节点 \(j\),我们要计算以 \(j\) 为根节点,有多少条边在 \(guesses\) 中。如果 \((i, j)\) 在 \(guesses\) 中,那么以 \(j\) 为根节点的,就不存在 \((i, j)\) 这条边,因此 \(cnt\) 的值要减去 \(1\)。如果 \((j, i)\) 在 \(guesses\) 中,那么以 \(j\) 为根节点的,要多出一条 \((i, j)\) 这条边,因此 \(cnt\) 的值要加上 \(1\)。即 \(f[j] = f[i] + (j, i) \in guesses - (i, j) \in guesses\)。其中 \(f[i]\) 表示以 \(i\) 为根节点,有多少条边在 \(guesses\) 中。
时间复杂度 \(O(n + m)\),空间复杂度 \(O(n + m)\)。其中 \(n\) 和 \(m\) 分别为 \(edges\) 和 \(guesses\) 的长度。
相似题目:
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 | class Solution:
def rootCount(
self, edges: List[List[int]], guesses: List[List[int]], k: int
) -> int:
def dfs1(i, fa):
nonlocal cnt
for j in g[i]:
if j != fa:
cnt += gs[(i, j)]
dfs1(j, i)
def dfs2(i, fa):
nonlocal ans, cnt
ans += cnt >= k
for j in g[i]:
if j != fa:
cnt -= gs[(i, j)]
cnt += gs[(j, i)]
dfs2(j, i)
cnt -= gs[(j, i)]
cnt += gs[(i, j)]
g = defaultdict(list)
for a, b in edges:
g[a].append(b)
g[b].append(a)
gs = Counter((u, v) for u, v in guesses)
cnt = 0
dfs1(0, -1)
ans = 0
dfs2(0, -1)
return ans
|
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 | class Solution {
private List<Integer>[] g;
private Map<Long, Integer> gs = new HashMap<>();
private int ans;
private int k;
private int cnt;
private int n;
public int rootCount(int[][] edges, int[][] guesses, int k) {
this.k = k;
n = edges.length + 1;
g = new List[n];
Arrays.setAll(g, e -> new ArrayList<>());
for (var e : edges) {
int a = e[0], b = e[1];
g[a].add(b);
g[b].add(a);
}
for (var e : guesses) {
int a = e[0], b = e[1];
gs.merge(f(a, b), 1, Integer::sum);
}
dfs1(0, -1);
dfs2(0, -1);
return ans;
}
private void dfs1(int i, int fa) {
for (int j : g[i]) {
if (j != fa) {
cnt += gs.getOrDefault(f(i, j), 0);
dfs1(j, i);
}
}
}
private void dfs2(int i, int fa) {
ans += cnt >= k ? 1 : 0;
for (int j : g[i]) {
if (j != fa) {
int a = gs.getOrDefault(f(i, j), 0);
int b = gs.getOrDefault(f(j, i), 0);
cnt -= a;
cnt += b;
dfs2(j, i);
cnt -= b;
cnt += a;
}
}
}
private long f(int i, int j) {
return 1L * i * n + j;
}
}
|
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49 | class Solution {
public:
int rootCount(vector<vector<int>>& edges, vector<vector<int>>& guesses, int k) {
int n = edges.size() + 1;
vector<int> g[n];
unordered_map<long long, int> gs;
auto f = [&](int i, int j) {
return 1LL * i * n + j;
};
for (auto& e : edges) {
int a = e[0], b = e[1];
g[a].push_back(b);
g[b].push_back(a);
}
for (auto& e : guesses) {
int a = e[0], b = e[1];
gs[f(a, b)]++;
}
int ans = 0;
int cnt = 0;
function<void(int, int)> dfs1 = [&](int i, int fa) {
for (int& j : g[i]) {
if (j != fa) {
cnt += gs[f(i, j)];
dfs1(j, i);
}
}
};
function<void(int, int)> dfs2 = [&](int i, int fa) {
ans += cnt >= k;
for (int& j : g[i]) {
if (j != fa) {
int a = gs[f(i, j)];
int b = gs[f(j, i)];
cnt -= a;
cnt += b;
dfs2(j, i);
cnt -= b;
cnt += a;
}
}
};
dfs1(0, -1);
dfs2(0, -1);
return ans;
}
};
|
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
36
37
38
39
40
41
42
43
44
45
46
47 | func rootCount(edges [][]int, guesses [][]int, k int) (ans int) {
n := len(edges) + 1
g := make([][]int, n)
gs := map[int]int{}
for _, e := range edges {
a, b := e[0], e[1]
g[a] = append(g[a], b)
g[b] = append(g[b], a)
}
f := func(i, j int) int {
return i*n + j
}
for _, e := range guesses {
a, b := e[0], e[1]
gs[f(a, b)]++
}
cnt := 0
var dfs1 func(i, fa int)
var dfs2 func(i, fa int)
dfs1 = func(i, fa int) {
for _, j := range g[i] {
if j != fa {
cnt += gs[f(i, j)]
dfs1(j, i)
}
}
}
dfs2 = func(i, fa int) {
if cnt >= k {
ans++
}
for _, j := range g[i] {
if j != fa {
a, b := gs[f(i, j)], gs[f(j, i)]
cnt -= a
cnt += b
dfs2(j, i)
cnt -= b
cnt += a
}
}
}
dfs1(0, -1)
dfs2(0, -1)
return
}
|
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
36
37
38
39
40
41 | function rootCount(edges: number[][], guesses: number[][], k: number): number {
const n = edges.length + 1;
const g: number[][] = Array.from({ length: n }, () => []);
const gs: Map<number, number> = new Map();
const f = (i: number, j: number) => i * n + j;
for (const [a, b] of edges) {
g[a].push(b);
g[b].push(a);
}
for (const [a, b] of guesses) {
const x = f(a, b);
gs.set(x, gs.has(x) ? gs.get(x)! + 1 : 1);
}
let ans = 0;
let cnt = 0;
const dfs1 = (i: number, fa: number): void => {
for (const j of g[i]) {
if (j !== fa) {
cnt += gs.get(f(i, j)) || 0;
dfs1(j, i);
}
}
};
const dfs2 = (i: number, fa: number): void => {
ans += cnt >= k ? 1 : 0;
for (const j of g[i]) {
if (j !== fa) {
const a = gs.get(f(i, j)) || 0;
const b = gs.get(f(j, i)) || 0;
cnt -= a;
cnt += b;
dfs2(j, i);
cnt -= b;
cnt += a;
}
}
};
dfs1(0, -1);
dfs2(0, -1);
return ans;
}
|