You are given an undirected graph that started as a tree with n
nodes labeled from 1
to n
, with one additional edge added. The input is a list of edges
, where each edge is a pair [u, v]
representing an undirected connection between nodes u
and v
.
The added edge introduces a cycle in the tree. Your task is to find the edge that, if removed, will restore the tree structure (i.e., the graph becomes acyclic and connected). If there are multiple candidates, return the edge that occurs last in the input edges
list.
At first glance, we might consider brute-forcing the solution by removing each edge one at a time and checking if the resulting graph is a tree (i.e., connected and acyclic). However, this is inefficient, especially for large graphs.
Instead, we should recognize that the problem is about detecting a cycle in a graph. Since the graph was a tree (which is acyclic) before one extra edge was added, the cycle must involve the new edge. Thus, the problem reduces to finding the first edge that, when added, creates a cycle. This is a classic use case for the Union-Find (Disjoint Set Union) data structure, which efficiently tracks connected components and can quickly determine if adding an edge would form a cycle.
We will use the Union-Find (Disjoint Set Union) data structure to solve this problem efficiently. Here’s the step-by-step approach:
[u, v]
in edges
:u
and v
are already connected (i.e., have the same root parent).Why Union-Find? Because it allows us to efficiently determine whether two nodes are already connected (cycle detection) in nearly constant time using path compression and union by rank optimizations.
Sample Input:
edges = [[1,2], [1,3], [2,3]]
The process checks each edge, and as soon as it finds that two nodes are already connected, it identifies the redundant edge.
class Solution:
def findRedundantConnection(self, edges):
parent = [i for i in range(len(edges) + 1)]
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(x, y):
px, py = find(x), find(y)
if px == py:
return False
parent[px] = py
return True
for u, v in edges:
if not union(u, v):
return [u, v]
class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
vector<int> parent(n + 1);
for (int i = 0; i <= n; ++i) parent[i] = i;
function<int(int)> find = [&](int x) {
if (parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
};
for (auto& edge : edges) {
int u = edge[0], v = edge[1];
int pu = find(u), pv = find(v);
if (pu == pv) return edge;
parent[pu] = pv;
}
return {};
}
};
class Solution {
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
int[] parent = new int[n + 1];
for (int i = 0; i <= n; i++) parent[i] = i;
for (int[] edge : edges) {
int u = edge[0], v = edge[1];
int pu = find(parent, u), pv = find(parent, v);
if (pu == pv) return edge;
parent[pu] = pv;
}
return new int[0];
}
private int find(int[] parent, int x) {
if (parent[x] != x)
parent[x] = find(parent, parent[x]);
return parent[x];
}
}
var findRedundantConnection = function(edges) {
let n = edges.length;
let parent = Array(n + 1).fill(0).map((_, i) => i);
function find(x) {
if (parent[x] !== x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
for (let [u, v] of edges) {
let pu = find(u), pv = find(v);
if (pu === pv) return [u, v];
parent[pu] = pv;
}
};
O(n^2)
, since for each edge you might need to perform a BFS/DFS to check connectivity and cycles.O(n)
for storing the graph and visited nodes.O(n \cdot \alpha(n))
, where \alpha(n)
is the inverse Ackermann function, which is nearly constant for practical values of n
. This is due to path compression and union by rank optimizations.O(n)
for the parent array.The Redundant Connection problem is a classic application of cycle detection in graphs. By recognizing that the problem reduces to finding the edge that creates a cycle in an almost-tree, we can efficiently solve it using the Union-Find data structure. This approach avoids expensive graph traversals and leverages efficient set operations to detect cycles as edges are added. The solution is both elegant and highly efficient, making it a great example of how foundational data structures can simplify complex problems.