算法训练营day57 | {108.冗余连接, 109.冗余连接II}

第11章图论-part06,继续并查集,今天是应用题,要将题意转化为并查类问题。

108. 冗余连接

1.1 解题分析

题目说是无向图,返回一条可以删去的边,使得结果图是一个有着N个节点的树(一个根节点)。如果有多个答案,则返回二维数组中最后出现的边。

解题思路:从前往后遍历每一条边,优先让前面的边先连上,如果边的两个节点不在同一个集合,就加入集合(即同一个根节点);如果边上的两个节点已经在同一个集合,那么再加一条边就会成环,此时找到答案。

1.2 解题小结

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
import java.util.Scanner;

public class Main {

static int[] father;

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
father = new int[n + 1];
for (int i = 0; i <= n; i++) {
father[i] = i;
}
while (n-- > 0) {
int u = scanner.nextInt();
int v = scanner.nextInt();

int fu = find(u);
int fv = find(v);
if (fu == fv) {
System.out.println(u + " " + v);
break;
}
father[fu] = fv;
}
}

private static int find(int u) {
if (u == father[u]) return u;
else {
father[u] = find(father[u]);
return father[u];
}
}
}

109. 冗余连接II

2.1 解题分析

正确的突破点是:有向图有一个节点的入度必定为2(情况1、2);或者情况3,n个节点依次连接成环,此时可以用并查集来删除边,和上一题一样。

情况1:删除任意一条边,都可以变成有向树;

情况2:只能删除特定边,删除另一条边后不符合一个有向树的定义。

2.2 解题小结

这道题代码比较难写,有空要再做一遍,主要是在Main类下定义静态内部类Edge、Disjoint之后会方便很多。作为工具类,Edge用于保存每条边,Disjoint构建并查集的模板,用于判断连通性。

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

public class Main {

static class Disjoint {
private final int[] father;

public Disjoint(int n) {
father = new int[n];
for (int i = 0; i < n; i++) {
father[i] = i;
}
}

public int find(int u) {
if (u == father[u]) return u;
return father[u] = find(father[u]);
}

public void join(int u, int v) {
u = find(u);
v = find(v);
if (u == v) return;
father[u] = v;
}

public boolean isSame(int u, int v) {
return find(u) == find(v);
}
}

static class Edge {
int s;
int t;

public Edge(int s, int t) {
this.s = s;
this.t = t;
}
}

static class Node {
int id;
int in;
int out;
}

public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
List<Edge> edges = new ArrayList<>();
Node[] nodeMap = new Node[n + 1];
for (int i = 1; i <= n; i++) {
nodeMap[i] = new Node();
}
Integer doubleIn = null;
for (int i = 0; i < n; i++) {
int s = scanner.nextInt();
int t = scanner.nextInt();
nodeMap[t].in++; // 记录入度
if (nodeMap[t].in == 2) doubleIn = t;
Edge edge = new Edge(s, t);
edges.add(edge);
}
Edge result = null;
if (doubleIn != null) {
List<Edge> doubleInEdges = new ArrayList<>();
for (Edge edge : edges) {
if (edge.t == doubleIn) doubleInEdges.add(edge);
if (doubleInEdges.size() == 2) break;
}
Edge edge = doubleInEdges.get(1);
if (isTreeAfterDelete(edges, edge, nodeMap)) {
result = edge;
} else {
result = doubleInEdges.get(0);
}
} else {
result = getRemoveEdge(edges, nodeMap);
}
System.out.println(result.s + " " + result.t);
}

private static Edge getRemoveEdge(List<Edge> edges, Node[] nodeMap) {
Disjoint disjoint = new Disjoint(nodeMap.length + 1);
for (Edge e : edges) {
if (disjoint.isSame(e.s, e.t)) return e;
disjoint.join(e.s, e.t);
}
return null;
}

private static boolean isTreeAfterDelete(List<Edge> edges, Edge deleteEdge, Node[] nodeMap) {
Disjoint disjoint = new Disjoint(nodeMap.length + 1);
for (Edge e : edges) {
if (e == deleteEdge) continue;
if (disjoint.isSame(e.s, e.t)) return false;
disjoint.join(e.s, e.t);
}
return true;
}
}

3. 今日收获

  • 学习时长:2小时

算法训练营day57 | {108.冗余连接, 109.冗余连接II}
http://paopaotangzu.xyz/cn/day57_leetcode/
作者
PROTON TANG
发布于
2026年2月18日
许可协议