Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Move from brute-force thinking to an efficient approach using union-find strategy.
In this problem, a tree is an undirected graph that is connected and has no cycles.
You are given a graph that started as a tree with n nodes labeled from 1 to n, with one additional edge added. The added edge has two different vertices chosen from 1 to n, and was not an edge that already existed. The graph is represented as an array edges of length n where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the graph.
Return an edge that can be removed so that the resulting graph is a tree of n nodes. If there are multiple answers, return the answer that occurs last in the input.
Example 1:
Input: edges = [[1,2],[1,3],[2,3]] Output: [2,3]
Example 2:
Input: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]] Output: [1,4]
Constraints:
n == edges.length3 <= n <= 1000edges[i].length == 21 <= ai < bi <= edges.lengthai != biProblem summary: In this problem, a tree is an undirected graph that is connected and has no cycles. You are given a graph that started as a tree with n nodes labeled from 1 to n, with one additional edge added. The added edge has two different vertices chosen from 1 to n, and was not an edge that already existed. The graph is represented as an array edges of length n where edges[i] = [ai, bi] indicates that there is an edge between nodes ai and bi in the graph. Return an edge that can be removed so that the resulting graph is a tree of n nodes. If there are multiple answers, return the answer that occurs last in the input.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Union-Find
[[1,2],[1,3],[2,3]]
[[1,2],[2,3],[3,4],[1,4],[1,5]]
redundant-connection-ii)accounts-merge)maximum-employees-to-be-invited-to-a-meeting)shortest-cycle-in-a-graph)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #684: Redundant Connection
class Solution {
private int[] p;
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
p = new int[n];
for (int i = 0; i < n; ++i) {
p[i] = i;
}
for (int i = 0;; ++i) {
int pa = find(edges[i][0] - 1);
int pb = find(edges[i][1] - 1);
if (pa == pb) {
return edges[i];
}
p[pa] = pb;
}
}
private int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
}
// Accepted solution for LeetCode #684: Redundant Connection
func findRedundantConnection(edges [][]int) []int {
n := len(edges)
p := make([]int, n)
for i := range p {
p[i] = i
}
var find func(int) int
find = func(x int) int {
if p[x] != x {
p[x] = find(p[x])
}
return p[x]
}
for i := 0; ; i++ {
pa, pb := find(edges[i][0]-1), find(edges[i][1]-1)
if pa == pb {
return edges[i]
}
p[pa] = pb
}
}
# Accepted solution for LeetCode #684: Redundant Connection
class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
def find(x: int) -> int:
if p[x] != x:
p[x] = find(p[x])
return p[x]
p = list(range(len(edges)))
for a, b in edges:
pa, pb = find(a - 1), find(b - 1)
if pa == pb:
return [a, b]
p[pa] = pb
// Accepted solution for LeetCode #684: Redundant Connection
use std::cmp::Ordering;
struct UnionFind {
parent: Vec<usize>,
rank: Vec<i32>,
}
impl UnionFind {
fn new(n: usize) -> Self {
UnionFind {
parent: (0..(n + 1)).collect(),
rank: vec![1; n + 1],
}
}
fn find(&mut self, n: usize) -> usize {
let mut p = self.parent[n];
while p != self.parent[p] {
self.parent[p] = self.parent[self.parent[p]];
p = self.parent[p];
}
p
}
fn union(&mut self, n1: usize, n2: usize) -> bool {
let p1 = self.find(n1);
let p2 = self.find(n2);
if p1 == p2 {
return false;
}
match self.rank[p1].cmp(&self.rank[p2]) {
Ordering::Greater => {
self.parent[p2] = p1;
self.rank[p1] += self.rank[p2];
}
_ => {
self.parent[p1] = p2;
self.rank[p2] = self.rank[p1];
}
}
true
}
}
impl Solution {
pub fn find_redundant_connection(edges: Vec<Vec<i32>>) -> Vec<i32> {
let mut union_find = UnionFind::new(edges.len() + 1);
for edge in edges {
let (n1, n2) = (edge[0] as usize, edge[1] as usize);
if !union_find.union(n1, n2) {
return vec![n1 as i32, n2 as i32];
}
}
unreachable!()
}
}
// Accepted solution for LeetCode #684: Redundant Connection
function findRedundantConnection(edges: number[][]): number[] {
const n = edges.length;
const p: number[] = Array.from({ length: n }, (_, i) => i);
const find = (x: number): number => {
if (p[x] !== x) {
p[x] = find(p[x]);
}
return p[x];
};
for (let i = 0; ; ++i) {
const pa = find(edges[i][0] - 1);
const pb = find(edges[i][1] - 1);
if (pa === pb) {
return edges[i];
}
p[pa] = pb;
}
}
Use this to step through a reusable interview workflow for this problem.
Track components with a list or adjacency matrix. Each union operation may need to update all n elements’ component labels, giving O(n) per union. For n union operations total: O(n²). Find is O(1) with direct lookup, but union dominates.
With path compression and union by rank, each find/union operation takes O(α(n)) amortized time, where α is the inverse Ackermann function — effectively constant. Space is O(n) for the parent and rank arrays. For m operations on n elements: O(m × α(n)) total.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.