cconnected.
Understand the pattern. Find your own way.Find your starting point
Your learning feed/Idea 71 of 218
Ideas this builds on
Graphs71 / 218

Redundant Connection

Picture

A–B and B–C already connect three nodes. When A–C arrives, its endpoints are already connected, so the new edge closes a cycle. A union that does nothing becomes the answer signal.

Predict

Why does processing input order return the requested last removable edge for a tree plus one extra edge?

Walk through the small example

There is exactly one cycle. Its final input edge is the first whose endpoints have already been joined along the rest of that cycle. It is therefore the failed union.

Mental model and complete Python solution

Before each edge, DSU represents connectivity using previous edges. Different roots mean the new edge joins two components. Equal roots mean an existing path already connects them, so adding the edge forms an undirected cycle. The implementation keeps the latest failure; under the tree-plus-one-edge contract there is exactly one.

What the function promises: Edges use positive integer labels and describe an undirected tree plus one extra edge, with no self-loops or parallel edges. Return the last removable edge in input order. Empty input returns None as a helper extension.

class DisjointSet:
    def __init__(self, n):
        self.parent = list(range(n))
        self.size = [1] * n

    def find(self, x):
        while x != self.parent[x]:
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    def union(self, a, b):
        a, b = self.find(a), self.find(b)
        if a == b:
            return False
        if self.size[a] < self.size[b]:
            a, b = b, a
        self.parent[b] = a
        self.size[a] += self.size[b]
        return True

def redundant_edge(edges):
    n = max((max(u, v) for u, v in edges), default=0)
    groups = DisjointSet(n + 1)
    answer = None
    for u, v in edges:
        if not groups.union(u, v):
            answer = (u, v)
    return answer

assert redundant_edge([(1, 2), (2, 3), (1, 3)]) == (1, 3)
assert redundant_edge([(1, 2), (1, 3), (2, 3)]) == (2, 3)
assert redundant_edge([]) is None

Work and storage: O(V + E α(V)) time and O(V) space for labels 1..V. Labels with huge gaps should be compressed instead of allocating up to their numeric maximum.

When to recognize it: An edge whose endpoints already share a group closes a cycle.

Check before applying it: This test is for undirected connectivity. A→B, A→C, B→C has no directed cycle even though ignoring arrows forms a triangle.

Change one rule

The edges are directed. Can this detect a directed cycle unchanged?

See what changes

No. A→B, A→C, B→C is acyclic but its undirected version closes a triangle. Use directed DFS with active/finished states or topological cycle detection.

Connect

Union-find: A failed union is useful information, not an error.

Critical Connections in a Network: Now ask the opposite question: which edge cannot be removed?

Problem reference: Redundant Connection (Medium). The walkthrough and Python solution above are original course material.