Menu
DSA interview questionsQuestion 105 of 147

DSA interview question · Question 105 of 147

Redundant Connection: Find the Edge That Closes a Cycle with Union-Find

  • Medium
  • coding
  • ~15 min
  • Medium relevance
  • 4 min read
  • Updated Oct 2026

Short answer

Process the edges in the given order with union-find. Each edge either joins two different sets, or connects two nodes that are already connected, which means it closes the cycle. Because the input is a tree plus one edge, and the answer must be the last cycle edge in input order, the first edge whose endpoints already share a root is that answer. Time is O(n * α(n)) and space is O(n).

On this page
  1. Problem
  2. Examples
  3. Approach 1: try removing edges from the end
  4. Approach 2: optimal, union-find
  5. Why the first failed union is the answer
  6. Template
  7. Python solution
  8. Complexity
  9. Tests
  10. Edge cases and pitfalls
  11. Where this shows up in data engineering

Problem

A tree with nodes labelled 1 to n had exactly one extra edge added between two different existing nodes, so the graph now has n edges and exactly one cycle. Given the edges in order, return an edge whose removal turns the graph back into a tree. If several edges would work, return the one that appears last in the input.

This is widely known as LeetCode 684, “Redundant Connection”.

Assume 3 to 1,000 nodes.

Examples

edges [[1,3],[3,5],[5,2],[2,1],[2,4]]
cycle: 1-3-5-2-1; candidates [1,3],[3,5],[5,2],[2,1]; last in input: [2,1]
-> [2,1]

edges [[2,3],[3,1],[1,2]]
-> [1,2]

edges [[2,4],[1,2],[4,1],[3,1]]
cycle 1-2-4-1, last cycle edge in input: [4,1]
-> [4,1]

Approach 1: try removing edges from the end

Walk the edges from last to first. For each, check whether the graph without that edge is connected (a connected graph with n - 1 edges is a tree). The first one that works is the answer.

def redundant_brute(edges):
    n = len(edges)
    for skip in range(n - 1, -1, -1):
        graph = {v: [] for v in range(1, n + 1)}
        for i, (a, b) in enumerate(edges):
            if i != skip:
                graph[a].append(b)
                graph[b].append(a)
        seen, stack = {1}, [1]
        while stack:
            for nxt in graph[stack.pop()]:
                if nxt not in seen:
                    seen.add(nxt)
                    stack.append(nxt)
        if len(seen) == n:
            return edges[skip]
    return []

Each check is O(n), so the whole thing is O(n^2).

Approach 2: optimal, union-find

Why the first failed union is the answer

Before the cycle is complete, every edge joins two separate trees, so unions succeed. The edge that completes the cycle is the first one whose endpoints are already connected. All other cycle edges appeared earlier in the input, so this edge is the last cycle edge in input order, which is exactly the tie-break the problem asks for.

Template

for (a, b) in edges:
    if find(a) == find(b): return [a, b]
    union(a, b)

Python solution

def find_redundant_connection(edges):
    n = len(edges)
    parent = list(range(n + 1))          # nodes are 1-based
    size = [1] * (n + 1)

    def find(v):
        while parent[v] != v:
            parent[v] = parent[parent[v]]
            v = parent[v]
        return v

    for a, b in edges:
        ra, rb = find(a), find(b)
        if ra == rb:
            return [a, b]
        if size[ra] < size[rb]:
            ra, rb = rb, ra
        parent[rb] = ra
        size[ra] += size[rb]
    return []

Complexity

  • Time: O(n * α(n)), effectively linear.
  • Space: O(n).

Tests

for fn in (find_redundant_connection, redundant_brute):
    assert fn([[1, 3], [3, 5], [5, 2], [2, 1], [2, 4]]) == [2, 1]
    assert fn([[2, 3], [3, 1], [1, 2]]) == [1, 2]          # smallest cycle
    assert fn([[2, 4], [1, 2], [4, 1], [3, 1]]) == [4, 1]
    # Cycle not involving node 1, extra edge in the middle of the list
    assert fn([[1, 2], [2, 3], [3, 4], [4, 2], [4, 5]]) == [4, 2]

# Larger ring: the closing edge is the last
ring = [[i, i + 1] for i in range(1, 500)] + [[500, 1]]
assert find_redundant_connection(ring) == [500, 1]

Edge cases and pitfalls

  • 1-based labels. Size the parent array n + 1, or shift labels down by one.
  • Returning the first cycle edge instead of the last. Union-find gives the right one for free, but a DFS that finds the cycle must then pick the cycle edge with the largest input index.
  • Directed edges. If edges are directed (each node has one parent), the problem becomes Redundant Connection II with a node possibly having two parents, and plain union-find is not enough.

Where this shows up in data engineering

When a hierarchy table (employee to manager, account to parent account) should be a tree, loading rows in order with union-find flags the exact row that introduces a loop. That is a cheap data-quality check to run before building a recursive rollup.

By Data Career Hub Editorial · Last reviewed Oct 2026 · Python 3 solutions verified with assert-based tests

Progress is saved in this browser only. No account needed.

Search
Filter by type