A tree of N nodes has exactly N-1 edges. Add one more and you create a cycle. Union-Find processes edges in order and flags the first one whose endpoints are already connected.
find(x) returns the representative root of x's set. union(a,b) merges two sets. Two nodes are connected iff they share the same root.
Process edges one by one; if find(u) == find(v) before uniting, that edge closes a cycle, it's the redundant one.
class DSU {
vector<int> parent, rnk;
public:
DSU(int n) : parent(n), rnk(n, 0) {
iota(parent.begin(), parent.end(), 0); // each its own root
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]); // path compress
return parent[x];
}
bool unite(int a, int b) {
a = find(a); b = find(b);
if (a == b) return false; // already connected → cycle
if (rnk[a] < rnk[b]) swap(a, b); // union by rank
parent[b] = a;
if (rnk[a] == rnk[b]) rnk[a]++;
return true;
}
};
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
DSU dsu(edges.size() + 1);
for (auto& e : edges)
if (!dsu.unite(e[0], e[1]))
return e; // first edge forming a cycle
return {};
}
α is the inverse Ackermann function, effectively constant.
Parent and rank arrays, one slot per node.
The earliest edge whose ends already share a root.