In this problem, a tree is an undirected graph that is connected and has no cycles.
You are given a graph that started as a tree with n nodes labeled from 1 to n, with one additional edge added. The added edge has two different vertices chosen from 1 to n, and was not an edge that already existed. The graph is represented as an array edges of length n where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the graph.
Return an edge that can be removed so that the resulting graph is a tree of n nodes. If there are multiple answers, return the answer that occurs last in the input.
Input: edges = [[1,2], [1,3], [2,3]]
Output: [2,3]
Explanation:
Initialize Union-Find:
Process Edges Sequentially:
Path Compression: When finding root, compress the path by making all nodes point directly to root
Union by Rank: When unioning, attach smaller tree under larger tree to keep tree height minimal
Sequential Processing: Since we want the last occurring redundant edge, we process edges in order
Union-Find Advantage: Perfect for dynamic connectivity queries - efficiently tracks which nodes belong to same component
Path Compression: Drastically improves Find operation by flattening tree structure
Union by Rank: Keeps trees balanced, preventing degeneration into linked lists
Cycle Detection: An edge is redundant if and only if it connects two nodes already in the same component
Find(x):
if parent[x] != x:
parent[x] = find(parent[x]) // Path compression
return parent[x]
Union(x, y):
rootX = find(x)
rootY = find(y)
if rootX == rootY:
return false // Already connected - cycle!
// Union by rank
if rank[rootX] > rank[rootY]:
parent[rootY] = rootX
else if rank[rootX] < rank[rootY]:
parent[rootX] = rootY
else:
parent[rootY] = rootX
rank[rootX]++
return true // Successfully united
1class Solution {
2 // Approach 1: Union-Find Solution
3 public int[] findRedundantConnection(int[][] edges) {
4 int n = edges.length;
5 int[] parent = new int[n + 1];
6 int[] rank = new int[n + 1];
7
8 // Initialize: each node is its own parent
9 for (int i = 1; i <= n; i++) {
10 parent[i] = i;
11 rank[i] = 0;
12 }
13
14 for (int[] edge : edges) {
15 int u = edge[0];
16 int v = edge[1];
17
18 int rootU = find(u, parent);
19 int rootV = find(v, parent);
20
21 // If same root, this edge creates a cycle
22 if (rootU == rootV) {
23 return edge;
24 }
25
26 // Union the two components
27 union(rootU, rootV, parent, rank);
28 }
29
30 return new int[]{};
31 }
32
33 private int find(int x, int[] parent) {
34 if (parent[x] != x) {
35 parent[x] = find(parent[x], parent); // Path compression
36 }
37 return parent[x];
38 }
39
40 private void union(int x, int y, int[] parent, int[] rank) {
41 // Union by rank
42 if (rank[x] > rank[y]) {
43 parent[y] = x;
44 } else if (rank[x] < rank[y]) {
45 parent[x] = y;
46 } else {
47 parent[y] = x;
48 rank[x]++;
49 }
50 }
51
52 // Approach 2: DFS Solution
53 public int[] findRedundantConnectionDFS(int[][] edges) {
54 int n = edges.length;
55 List<List<Integer>> graph = new ArrayList<>();
56
57 for (int i = 0; i <= n; i++) {
58 graph.add(new ArrayList<>());
59 }
60
61 for (int[] edge : edges) {
62 int u = edge[0];
63 int v = edge[1];
64
65 // Check if u and v are already connected
66 Set<Integer> visited = new HashSet<>();
67 if (hasPath(u, v, graph, visited)) {
68 return edge; // This edge creates a cycle
69 }
70
71 // Add edge to graph
72 graph.get(u).add(v);
73 graph.get(v).add(u);
74 }
75
76 return new int[]{};
77 }
78
79 private boolean hasPath(int start, int target, List<List<Integer>> graph, Set<Integer> visited) {
80 if (start == target) return true;
81 visited.add(start);
82
83 for (int neighbor : graph.get(start)) {
84 if (!visited.contains(neighbor)) {
85 if (hasPath(neighbor, target, graph, visited)) {
86 return true;
87 }
88 }
89 }
90
91 return false;
92 }
93
94 // Approach 3: Union-Find with Simple Path Compression
95 public int[] findRedundantConnectionSimple(int[][] edges) {
96 int[] parent = new int[edges.length + 1];
97
98 for (int i = 1; i < parent.length; i++) {
99 parent[i] = i;
100 }
101
102 for (int[] edge : edges) {
103 int x = edge[0];
104 int y = edge[1];
105
106 // Find roots
107 while (parent[x] != x) x = parent[x];
108 while (parent[y] != y) y = parent[y];
109
110 if (x == y) {
111 return edge; // Cycle detected
112 }
113
114 parent[x] = y; // Union
115 }
116
117 return new int[]{};
118 }
119}
120