LeetCode 684: Redundant Connection (Union-Find Cycle Detection)
LeetCode 684GraphUnion FindToday we solve LeetCode 684 - Redundant Connection.
Source: https://leetcode.com/problems/redundant-connection/
English
Problem Summary
You are given an undirected graph that started as a tree on nodes 1..n, then one extra edge was added. Return the edge that can be removed to restore a tree. If multiple answers exist, return the one that appears last in the input.
Key Insight
In a tree, any two nodes have exactly one path. While scanning edges in order, if two endpoints are already connected, adding this edge forms a cycle. That edge is the redundant one.
Algorithm
- Initialize DSU (Union-Find) for all nodes.
- For each edge [u, v], attempt union(u, v).
- If union fails (same root), return this edge immediately.
- Constraints guarantee one answer exists.
Complexity Analysis
With path compression + union by rank:
Time: O(n α(n)).
Space: O(n).
Reference Implementations (Java / Go / C++ / Python / JavaScript)
class Solution {
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
DSU dsu = new DSU(n + 1);
for (int[] e : edges) {
if (!dsu.union(e[0], e[1])) return e;
}
return new int[0];
}
static class DSU {
int[] parent, rank;
DSU(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false;
if (rank[ra] < rank[rb]) parent[ra] = rb;
else if (rank[ra] > rank[rb]) parent[rb] = ra;
else { parent[rb] = ra; rank[ra]++; }
return true;
}
}
}func findRedundantConnection(edges [][]int) []int {
n := len(edges)
parent := make([]int, n+1)
rank := make([]int, n+1)
for i := 0; i <= n; i++ {
parent[i] = i
}
var find func(int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
union := func(a, b int) bool {
ra, rb := find(a), find(b)
if ra == rb {
return false
}
if rank[ra] < rank[rb] {
parent[ra] = rb
} else if rank[ra] > rank[rb] {
parent[rb] = ra
} else {
parent[rb] = ra
rank[ra]++
}
return true
}
for _, e := range edges {
if !union(e[0], e[1]) {
return e
}
}
return []int{}
}class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
vector<int> parent(n + 1), rank(n + 1, 0);
iota(parent.begin(), parent.end(), 0);
function<int(int)> find = [&](int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
};
auto unite = [&](int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false;
if (rank[ra] < rank[rb]) parent[ra] = rb;
else if (rank[ra] > rank[rb]) parent[rb] = ra;
else { parent[rb] = ra; rank[ra]++; }
return true;
};
for (auto& e : edges) {
if (!unite(e[0], e[1])) return e;
}
return {};
}
};class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges)
parent = list(range(n + 1))
rank = [0] * (n + 1)
def find(x: int) -> int:
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(a: int, b: int) -> bool:
ra, rb = find(a), find(b)
if ra == rb:
return False
if rank[ra] < rank[rb]:
parent[ra] = rb
elif rank[ra] > rank[rb]:
parent[rb] = ra
else:
parent[rb] = ra
rank[ra] += 1
return True
for u, v in edges:
if not union(u, v):
return [u, v]
return []var findRedundantConnection = function(edges) {
const n = edges.length;
const parent = Array.from({ length: n + 1 }, (_, i) => i);
const rank = new Array(n + 1).fill(0);
const find = (x) => {
if (parent[x] !== x) parent[x] = find(parent[x]);
return parent[x];
};
const union = (a, b) => {
let ra = find(a), rb = find(b);
if (ra === rb) return false;
if (rank[ra] < rank[rb]) parent[ra] = rb;
else if (rank[ra] > rank[rb]) parent[rb] = ra;
else {
parent[rb] = ra;
rank[ra]++;
}
return true;
};
for (const [u, v] of edges) {
if (!union(u, v)) return [u, v];
}
return [];
};中文
题目概述
给定一个无向图,它最初是一棵包含 1..n 的树,后来额外添加了一条边。请找出这条“冗余边”,删除后图重新成为树。若有多个候选答案,返回输入顺序中最后出现的一条。
核心思路
树中任意两点只有一条简单路径。按顺序处理边并查集时,若一条边的两个端点已经连通,再加入它就会形成环,这条边就是答案。
算法步骤
- 初始化并查集 parent/rank。
- 依次处理每条边 [u, v]。
- 如果 union(u, v) 失败(同根),立即返回该边。
- 题目保证存在唯一可删除边。
复杂度分析
使用路径压缩和按秩合并:
时间复杂度:O(n α(n))。
空间复杂度:O(n)。
多语言参考实现(Java / Go / C++ / Python / JavaScript)
class Solution {
public int[] findRedundantConnection(int[][] edges) {
int n = edges.length;
DSU dsu = new DSU(n + 1);
for (int[] e : edges) {
if (!dsu.union(e[0], e[1])) return e;
}
return new int[0];
}
static class DSU {
int[] parent, rank;
DSU(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
boolean union(int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false;
if (rank[ra] < rank[rb]) parent[ra] = rb;
else if (rank[ra] > rank[rb]) parent[rb] = ra;
else { parent[rb] = ra; rank[ra]++; }
return true;
}
}
}func findRedundantConnection(edges [][]int) []int {
n := len(edges)
parent := make([]int, n+1)
rank := make([]int, n+1)
for i := 0; i <= n; i++ {
parent[i] = i
}
var find func(int) int
find = func(x int) int {
if parent[x] != x {
parent[x] = find(parent[x])
}
return parent[x]
}
union := func(a, b int) bool {
ra, rb := find(a), find(b)
if ra == rb {
return false
}
if rank[ra] < rank[rb] {
parent[ra] = rb
} else if rank[ra] > rank[rb] {
parent[rb] = ra
} else {
parent[rb] = ra
rank[ra]++
}
return true
}
for _, e := range edges {
if !union(e[0], e[1]) {
return e
}
}
return []int{}
}class Solution {
public:
vector<int> findRedundantConnection(vector<vector<int>>& edges) {
int n = edges.size();
vector<int> parent(n + 1), rank(n + 1, 0);
iota(parent.begin(), parent.end(), 0);
function<int(int)> find = [&](int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
};
auto unite = [&](int a, int b) {
int ra = find(a), rb = find(b);
if (ra == rb) return false;
if (rank[ra] < rank[rb]) parent[ra] = rb;
else if (rank[ra] > rank[rb]) parent[rb] = ra;
else { parent[rb] = ra; rank[ra]++; }
return true;
};
for (auto& e : edges) {
if (!unite(e[0], e[1])) return e;
}
return {};
}
};class Solution:
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
n = len(edges)
parent = list(range(n + 1))
rank = [0] * (n + 1)
def find(x: int) -> int:
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]
def union(a: int, b: int) -> bool:
ra, rb = find(a), find(b)
if ra == rb:
return False
if rank[ra] < rank[rb]:
parent[ra] = rb
elif rank[ra] > rank[rb]:
parent[rb] = ra
else:
parent[rb] = ra
rank[ra] += 1
return True
for u, v in edges:
if not union(u, v):
return [u, v]
return []var findRedundantConnection = function(edges) {
const n = edges.length;
const parent = Array.from({ length: n + 1 }, (_, i) => i);
const rank = new Array(n + 1).fill(0);
const find = (x) => {
if (parent[x] !== x) parent[x] = find(parent[x]);
return parent[x];
};
const union = (a, b) => {
let ra = find(a), rb = find(b);
if (ra === rb) return false;
if (rank[ra] < rank[rb]) parent[ra] = rb;
else if (rank[ra] > rank[rb]) parent[rb] = ra;
else {
parent[rb] = ra;
rank[ra]++;
}
return true;
};
for (const [u, v] of edges) {
if (!union(u, v)) return [u, v];
}
return [];
};
Comments