Redundant Connection

MediumGraphs · Union-Find🧩 Union-FindLeetCode #684 ↗Updated

Problem

A tree on nodes 1..n had one extra edge added, so the undirected graph given as edges (n edges) now contains exactly one cycle. Return an edge whose removal turns the graph back into a tree; if several qualify, return the one that appears last in edges.
Input[[1,2],[1,3],[2,3]]Output[2,3]
Input[[1,2],[2,3],[3,4],[1,4],[1,5]]Output[1,4]

n == edges.length  ·  3 ≤ n ≤ 1000  ·  1 ≤ ai < bi ≤ n  ·  distinct edges, connected

Try it yourself

Java
class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Add edges in input order: the first edge whose endpoints are already connected closes the only cycle, and it is the latest cycle edge in the input. Union-Find answers “already connected?” in near-constant time.
PatternUnion-Find

Instinct Items get grouped by "connected", "same as" or "shares a" links and you keep asking which group something is in: merge sets with Union-Find.

Memory hack Find your boss, merge the bosses; squash the chain (path compression) and hang the short tree under the tall one (rank).

Open the Union-Find guide →

Approaches

Brute ForceIncremental Graph + DFS Connectivity Check

Time O(n2)Space O(n)

Build the graph edge by edge. Before adding (u, v), run a DFS from u: if v is already reachable, this edge closes the cycle. The cycle only appears once its last edge (in input order) arrives, so the first hit is the answer.

Java
public int[] findRedundantConnection(int[][] edges) {
    int n = edges.length;
    List<List<Integer>> adj = new ArrayList<>();
    for (int i = 0; i <= n; i++) adj.add(new ArrayList<>());
    for (int[] e : edges) {
        if (connected(adj, e[0], e[1], new boolean[n + 1])) return e;
        adj.get(e[0]).add(e[1]); adj.get(e[1]).add(e[0]);
    }
    return new int[0];
}
private boolean connected(List<List<Integer>> adj, int u, int target, boolean[] seen) {
    if (u == target) return true;
    seen[u] = true;
    for (int v : adj.get(u))
        if (!seen[v] && connected(adj, v, target, seen)) return true;
    return false;
}

Simple and correct, but one O(n) DFS per edge ⇒ quadratic.

At a glance

LevelApproachTimeSpace
Brute ForceIncremental Graph + DFS Connectivity CheckO(n2)O(n)
BetterOne DFS: Find the Cycle, Pick Its Last EdgeO(n)O(n)
OptimalUnion-Find (Path Compression + Union by Rank)O(n α(n))O(n)
Common pitfalls
  • Returning the first cycle edge in input order instead of the last.
  • Off-by-one: nodes are 1-indexed, so size the arrays n + 1.
  • In undirected DFS, treating the edge back to the parent as a cycle.
Interview tips
  • Note n nodes + n edges + connected ⇒ exactly one cycle.
  • Explain why the first failing union is the last cycle edge in the input.
  • Mention α(n) ≤ 4 in practice; the DSU is effectively linear.
Pattern
Union-Find → edges arrive one by one and you must ask “are these already connected?”
My notes saved in this browser