Algorithm

Redundant Connection

Graphs Pattern

Redundant Connection

You are given a graph that began as a tree with n nodes labelled 1 to n, to which exactly one additional edge was added. The result is a connected undirected graph with n nodes and n edges, containing exactly one cycle.

Return an edge whose removal restores a tree — that is, leaves the graph connected and acyclic. Any edge on the cycle would work, so when several are valid, return the one appearing last in the input list.

CONSTRAINTS
  • n == edges.length
  • 3 <= n <= 1000
  • edges[i] = [aᵢ, bᵢ] with 1 <= aᵢ < bᵢ <= n
  • The graph is connected and contains exactly one cycle
  • There are no self-loops and no repeated edges
EXAMPLE 1
Input: edges = [[1,2],[1,3],[2,3]]
Output: [2,3]
The three edges form a triangle among nodes 1, 2 and 3. Removing any one of them leaves a valid tree, so all three are candidates — the tie-break rule selects the last one in the list.
EXAMPLE 2
Input: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
Output: [1,4]
The cycle is 1-2-3-4-1, formed once [1,4] joins two nodes already linked through 2 and 3. The edge [1,5] comes later in the list but is not on the cycle at all — node 5 dangles off the side, so removing it would disconnect the graph rather than fix it.
EXAMPLE 3
Input: edges = [[1,2],[2,3],[1,3]]
Output: [1,3]
Same triangle as the first example but listed in a different order, and the answer changes accordingly. The tie-break depends on input order, not on the graph's shape.
EXAMPLE 4
Input: edges = [[1,2],[1,3],[1,4],[2,3]]
Output: [2,3]
Node 1 is joined to 2, 3 and 4, forming a star. The edge [2,3] then links two nodes already connected through node 1, closing the triangle 1-2-3-1. Node 4 remains a leaf and is untouched.
How many cycles can the graph contain?
Exactly one. A tree on n nodes has n-1 edges, and here there are n, so precisely one edge is surplus and it creates precisely one cycle. That guarantee is what makes a single pass sufficient.
If several edges could be removed, which should I return?
The one occurring last in the input. Any edge on the cycle restores a tree when removed, so the rule exists purely to make the answer unique. Processing edges in order and returning the first that closes a cycle satisfies it automatically.
Does the order of the two nodes within an edge matter?
No — the graph is undirected, so [u, v] and [v, u] describe the same edge. Return the pair in the form it was given.
Are the nodes labelled from 0 or from 1?
From 1 to n, which means an array-based parent structure needs n+1 slots or a consistent offset. Off-by-one here is the most common implementation bug on this problem.

Start with the counting fact the problem is built on, because it explains why the situation is so tightly constrained.

Why exactly one edge is surplus

A tree is a graph that is connected and has no cycles. Such a graph on n nodes always has exactly n-1 edges — no more, no fewer.

The reason is worth seeing rather than memorising. Build a tree by adding nodes one at a time: the first node needs no edge, and every subsequent node must be attached to the existing structure by exactly one edge — zero edges would leave it disconnected, and two or more would create a second route between two nodes, which is a cycle. So n nodes require n-1 edges.

Here you are given n nodes and n edges. That is one edge too many, and the surplus edge must have joined two nodes that were already connected, creating exactly one cycle. So the problem is not "find a cycle" — you already know one exists. It is identify which edge closed it.

The direct approach

Process edges in order and build the graph as you go. Before adding edge (u, v), check whether u and v are already connected by the edges added so far — a traversal from u looking for v. If they are, this edge closes the cycle and is the answer.

That is correct, and the tie-break falls out of it: processing in input order means the first edge found to close a cycle is the last-listed edge of the cycle, which is what the problem asks for.

The cost is the problem. Each check is a traversal costing O(n), performed for up to n edges, giving O(n²). Fine at n = 1000, but it repeats a great deal — each traversal re-derives connectivity information that the previous traversals already established and then discarded.

The reframe: track groups instead of routes

The check does not need a route between u and v. It needs a single yes-or-no: are they already in the same connected group?

So maintain the grouping directly. Give each group a representative — one distinguished member — and store, for each node, a pointer toward its group's representative. Then two nodes are connected exactly when their representatives match, which is two lookups and a comparison instead of a search.

Merging is equally cheap. To join two groups, point one representative at the other; every node that followed the first now reaches the second by one extra step. A single pointer write merges groups of any size. This structure is Union-Find.

The algorithm becomes: for each edge in order, look up both representatives. If they differ, the edge connects two previously separate groups — keep it, and merge. If they match, the two nodes were already connected by earlier edges, so this edge closes a loop. Return it.

python
def find_redundant_connection(edges):
    n = len(edges)
    parent = list(range(n + 1))               # nodes are 1..n, so size n+1
    rank = [0] * (n + 1)

    def find(x):
        while parent[x] != x:
            parent[x] = parent[parent[x]]     # flatten the path while walking it
            x = parent[x]
        return x

    for u, v in edges:
        ru, rv = find(u), find(v)
        if ru == rv:
            return [u, v]                     # already connected: this edge closes the cycle
        if rank[ru] < rank[rv]:               # attach the shorter tree under the taller
            ru, rv = rv, ru
        parent[rv] = ru
        if rank[ru] == rank[rv]:
            rank[ru] += 1
    return []                                 # unreachable given the problem's guarantee
Crucial Notethe parent array is sized n+1, because nodes are labelled 1 to n rather than 0 to n-1. Sizing it n leaves the highest-numbered node out of range, and index 0 simply goes unused. This off-by-one is the most frequent bug on this problem.
Crucial Notereturn as soon as a redundant edge is found. Continuing past it would merge the cycle's endpoints anyway and later edges might also appear redundant, but the guarantee of exactly one surplus edge means the first hit is the answer — and stopping is what makes the tie-break correct.

The two optimisations keep it fast. Path compression — repointing nodes at the root while walking up — means a slow lookup pays to eliminate itself. Union by rank — attaching the shorter tree beneath the taller — stops chains forming in the first place. Together they give O(α(n)) per operation, where α is the inverse Ackermann function, below 5 for any conceivable input. Total: O(n · α(n)), effectively linear.

Worked example

edges = [[1,2],[2,3],[3,4],[1,4],[1,5]].

Every node starts as its own representative: {1}, {2}, {3}, {4}, {5}.

- [1,2]. find(1) is 1, find(2) is 2. Different, so merge. Groups: {1,2}, {3}, {4}, {5}.
- [2,3]. find(2) reaches the merged root, find(3) is 3. Different, so merge. Groups: {1,2,3}, {4}, {5}.
- [3,4]. find(3) reaches the same root, find(4) is 4. Different, so merge. Groups: {1,2,3,4}, {5}.
- [1,4]. find(1) and find(4) now return the same representative — nodes 1 and 4 were already connected through 2 and 3. This edge adds a second route between them, closing the cycle 1-2-3-4-1. Return [1,4].

The edge [1,5] is never examined. Worth noticing that it appears later in the list yet is not the answer: the tie-break is "the last edge of the cycle", not the last edge overall. Node 5 hangs off node 1 as a leaf, and removing [1,5] would disconnect it rather than break any loop.

For the triangle [[1,2],[1,3],[2,3]]: the first two edges merge cleanly, putting 1, 2 and 3 in one group. The third finds 2 and 3 already sharing a representative and returns [2,3]. Reorder the input as [[1,2],[2,3],[1,3]] and the same logic returns [1,3] — the graph is identical, but the answer follows the input order.

The transferable idea

The move to extract is turning an expensive question into a cheap one by maintaining the answer instead of computing it. "Are these two connected?" costs a traversal when asked cold, and costs two array lookups when the grouping is kept up to date as edges arrive. The extra bookkeeping during each merge is repaid many times over.

The recognition signal is specific and reliable: edges arriving one at a time, with connectivity queried in between. That combination is Union-Find. If instead the whole graph is given up front and asked about once, a traversal is usually simpler.

The same shape appears throughout. Detecting a cycle in an undirected graph is this exact test. Kruskal's minimum spanning tree algorithm is this plus sorting edges by weight — take each edge cheapest-first and keep it only if the union succeeds, which is precisely "keep it unless it closes a cycle". Counting connected components is this while tracking how many merges succeeded.

Train the reflex: when an edge would join two nodes that are already connected, that edge is redundant by definition. The only real question is how cheaply you can tell — and maintaining group representatives makes it nearly free.

Interactive Strategy Visualization

Union-Find Evolution

Dynamic Connectivity
O(α(N)) Complexity
0
1
2
3
4
5

Phase 1: Initial State

Every element starts as its own representative (Leader). Each node points to itself.

O(n²) Reachability Search Before Each Edge
O(n · α(n)) Union-Find Incremental Merging