Redundant Connection
Find the edge that creates a cycle in a tree-augmented graph — Union-Find detects the merge that closes a loop.
5 min read
union-find graph
Problem#
Given an undirected graph that was originally a tree with one extra edge added, return the extra edge. If multiple answers exist, return the one that appears last in the input.
Examples#
[[1,2],[1,3],[2,3]]→[2,3].[[1,2],[2,3],[3,4],[1,4],[1,5]]→[1,4].
Constraints#
3 <= edges.length <= 1000. Nodes are1..n.
Hints#
Hint
Union-Find: an edge is redundant iff both endpoints are already in the same component.
Approach#
DSU with path compression and union by rank. Process edges in order; the first edge whose endpoints already share a root is the answer.
Solution#
class Solution { vector<int> parent, rnk; int find(int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; } bool unite(int a, int b) { a = find(a); b = find(b); if (a == b) return false; if (rnk[a] < rnk[b]) swap(a, b); parent[b] = a; if (rnk[a] == rnk[b]) ++rnk[a]; return true; }public: vector<int> findRedundantConnection(vector<vector<int>>& edges) { int n = edges.size(); parent.resize(n + 1); rnk.assign(n + 1, 0); iota(parent.begin(), parent.end(), 0); for (auto& e : edges) { if (!unite(e[0], e[1])) return e; } return {}; }};func findRedundantConnection(edges [][]int) []int { n := len(edges) parent := make([]int, n+1) rnk := make([]int, n+1) for i := range parent { parent[i] = i } var find func(x int) int find = func(x int) int { for parent[x] != x { parent[x] = parent[parent[x]] x = parent[x] } return x } unite := func(a, b int) bool { a, b = find(a), find(b) if a == b { return false } if rnk[a] < rnk[b] { a, b = b, a } parent[b] = a if rnk[a] == rnk[b] { rnk[a]++ } return true } for _, e := range edges { if !unite(e[0], e[1]) { return e } } return nil}from typing import List
class Solution: def findRedundantConnection(self, edges: List[List[int]]) -> List[int]: n = len(edges) parent = list(range(n + 1)) rnk = [0] * (n + 1)
def find(x: int) -> int: while parent[x] != x: parent[x] = parent[parent[x]] x = parent[x] return x
def unite(a: int, b: int) -> bool: a, b = find(a), find(b) if a == b: return False if rnk[a] < rnk[b]: a, b = b, a parent[b] = a if rnk[a] == rnk[b]: rnk[a] += 1 return True
for e in edges: if not unite(e[0], e[1]): return e return []function findRedundantConnection(edges) { const n = edges.length; const parent = new Array(n + 1); const rnk = new Array(n + 1).fill(0); for (let i = 0; i <= n; i++) parent[i] = i; const find = (x) => { while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }; const unite = (a, b) => { a = find(a); b = find(b); if (a === b) return false; if (rnk[a] < rnk[b]) [a, b] = [b, a]; parent[b] = a; if (rnk[a] === rnk[b]) rnk[a]++; return true; }; for (const e of edges) { if (!unite(e[0], e[1])) return e; } return [];}class Solution { private int[] parent; private int[] rnk;
public int[] findRedundantConnection(int[][] edges) { int n = edges.length; parent = new int[n + 1]; rnk = new int[n + 1]; for (int i = 0; i <= n; i++) parent[i] = i; for (int[] e : edges) { if (!unite(e[0], e[1])) return e; } return new int[0]; }
private int find(int x) { while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }
private boolean unite(int a, int b) { a = find(a); b = find(b); if (a == b) return false; if (rnk[a] < rnk[b]) { int t = a; a = b; b = t; } parent[b] = a; if (rnk[a] == rnk[b]) rnk[a]++; return true; }}function findRedundantConnection(edges: number[][]): number[] { const n = edges.length; const parent: number[] = new Array<number>(n + 1); const rnk: number[] = new Array<number>(n + 1).fill(0); for (let i = 0; i <= n; i++) parent[i] = i; const find = (x: number): number => { while (parent[x] !== x) { parent[x] = parent[parent[x]]; x = parent[x]; } return x; }; const unite = (a: number, b: number): boolean => { a = find(a); b = find(b); if (a === b) return false; if (rnk[a] < rnk[b]) [a, b] = [b, a]; parent[b] = a; if (rnk[a] === rnk[b]) rnk[a]++; return true; }; for (const e of edges) { if (!unite(e[0], e[1])) return e; } return [];}Editorial#
A tree of n nodes has exactly n - 1 edges. Adding one more edge necessarily creates exactly one cycle, and Union-Find catches this when it sees two endpoints that already share a root. Processing edges in input order ensures we return the last-added one that closes the cycle.
Complexity#
- Time: O(N * α(N)).
- Space: O(N).
Concept revision#
Related#