🧠
Union-Find (Disjoint Set Union) Approach
💡 Union-Find is a classic data structure to efficiently track connected components. This approach is faster and cleaner than DFS for large inputs and is a must-know technique for network connectivity problems.
Intuition
Use union-find to merge connected computers. Count how many connected components remain. If cables are insufficient, return -1. Otherwise, operations needed equals components minus one.
Algorithm
- Initialize union-find data structure with n separate sets.
- For each connection, union the two computers.
- Count how many unique parents (connected components) remain.
- If cables < n - 1, return -1; else return components - 1.
💡 Union-find abstracts away graph traversal and provides near O(1) union and find operations, making it ideal for large datasets.
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
self.count = n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
rootX = self.find(x)
rootY = self.find(y)
if rootX != rootY:
if self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
elif self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
self.count -= 1
def makeConnected(n, connections):
if len(connections) < n - 1:
return -1
uf = UnionFind(n)
for u, v in connections:
uf.union(u, v)
return uf.count - 1
# Driver code
if __name__ == '__main__':
print(makeConnected(4, [[0,1],[0,2],[1,2]])) # 1
print(makeConnected(6, [[0,1],[0,2],[0,3],[1,2],[1,3]])) # 2
print(makeConnected(6, [[0,1],[0,2],[0,3],[1,2]])) # -1
Line Notes
self.parent = list(range(n))Initialize each node as its own parent (self root)
self.rank = [0] * nRank array to keep tree shallow during unions
if self.parent[x] != x:Path compression to flatten tree for fast finds
if rootX != rootY:Only union if roots differ to avoid cycles
self.count -= 1Decrement count of connected components after union
if len(connections) < n - 1:Check if enough cables exist before union operations
uf = UnionFind(n)Create union-find instance for n computers
for u, v in connections:Union connected computers
return uf.count - 1Operations needed equals number of components minus one
class UnionFind {
int[] parent, rank;
int count;
public UnionFind(int n) {
parent = new int[n];
rank = new int[n];
count = n;
for (int i = 0; i < n; i++) parent[i] = i;
}
public int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
public void union(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] < rank[rootY]) parent[rootX] = rootY;
else if (rank[rootX] > rank[rootY]) parent[rootY] = rootX;
else {
parent[rootY] = rootX;
rank[rootX]++;
}
count--;
}
}
}
public class Solution {
public int makeConnected(int n, int[][] connections) {
if (connections.length < n - 1) return -1;
UnionFind uf = new UnionFind(n);
for (int[] conn : connections) uf.union(conn[0], conn[1]);
return uf.count - 1;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.makeConnected(4, new int[][]{{0,1},{0,2},{1,2}})); // 1
System.out.println(sol.makeConnected(6, new int[][]{{0,1},{0,2},{0,3},{1,2},{1,3}})); // 2
System.out.println(sol.makeConnected(6, new int[][]{{0,1},{0,2},{0,3},{1,2}})); // -1
}
}
Line Notes
parent = new int[n];Initialize parent array where each node is its own parent
rank = new int[n];Rank array to optimize union operations
if (parent[x] != x) parent[x] = find(parent[x]);Path compression to speed up find
if (rootX != rootY)Union only if roots differ to avoid cycles
count--;Decrease count of connected components after union
if (connections.length < n - 1) return -1;Check if enough cables exist before unions
UnionFind uf = new UnionFind(n);Create union-find instance
for (int[] conn : connections) uf.union(conn[0], conn[1]);Union connected computers
return uf.count - 1;Operations needed equals components minus one
#include <iostream>
#include <vector>
using namespace std;
class UnionFind {
public:
vector<int> parent, rank;
int count;
UnionFind(int n) : parent(n), rank(n, 0), count(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];
}
void unite(int x, int y) {
int rootX = find(x), rootY = find(y);
if (rootX != rootY) {
if (rank[rootX] < rank[rootY]) parent[rootX] = rootY;
else if (rank[rootX] > rank[rootY]) parent[rootY] = rootX;
else {
parent[rootY] = rootX;
rank[rootX]++;
}
count--;
}
}
};
class Solution {
public:
int makeConnected(int n, vector<vector<int>>& connections) {
if (connections.size() < n - 1) return -1;
UnionFind uf(n);
for (auto& conn : connections) uf.unite(conn[0], conn[1]);
return uf.count - 1;
}
};
// Driver code
int main() {
Solution sol;
vector<vector<int>> con1 = {{0,1},{0,2},{1,2}};
cout << sol.makeConnected(4, con1) << "\n"; // 1
vector<vector<int>> con2 = {{0,1},{0,2},{0,3},{1,2},{1,3}};
cout << sol.makeConnected(6, con2) << "\n"; // 2
vector<vector<int>> con3 = {{0,1},{0,2},{0,3},{1,2}};
cout << sol.makeConnected(6, con3) << "\n"; // -1
return 0;
}
Line Notes
vector<int> parent, rank;Parent and rank arrays for union-find structure
for (int i = 0; i < n; i++) parent[i] = i;Initialize each node as its own parent
if (parent[x] != x) parent[x] = find(parent[x]);Path compression to speed up find
if (rootX != rootY)Union only if roots differ to avoid cycles
count--;Decrement connected components count after union
if (connections.size() < n - 1) return -1;Check if enough cables exist before union operations
UnionFind uf(n);Create union-find instance
for (auto& conn : connections) uf.unite(conn[0], conn[1]);Union connected computers
return uf.count - 1;Operations needed equals components minus one
class UnionFind {
constructor(n) {
this.parent = Array.from({length: n}, (_, i) => i);
this.rank = new Array(n).fill(0);
this.count = n;
}
find(x) {
if (this.parent[x] !== x) {
this.parent[x] = this.find(this.parent[x]);
}
return this.parent[x];
}
union(x, y) {
let rootX = this.find(x);
let rootY = this.find(y);
if (rootX !== rootY) {
if (this.rank[rootX] < this.rank[rootY]) {
this.parent[rootX] = rootY;
} else if (this.rank[rootX] > this.rank[rootY]) {
this.parent[rootY] = rootX;
} else {
this.parent[rootY] = rootX;
this.rank[rootX]++;
}
this.count--;
}
}
}
function makeConnected(n, connections) {
if (connections.length < n - 1) return -1;
const uf = new UnionFind(n);
for (const [u, v] of connections) {
uf.union(u, v);
}
return uf.count - 1;
}
// Driver code
console.log(makeConnected(4, [[0,1],[0,2],[1,2]])); // 1
console.log(makeConnected(6, [[0,1],[0,2],[0,3],[1,2],[1,3]])); // 2
console.log(makeConnected(6, [[0,1],[0,2],[0,3],[1,2]])); // -1
Line Notes
this.parent = Array.from({length: n}, (_, i) => i);Initialize each node as its own parent
this.rank = new Array(n).fill(0);Rank array to keep trees shallow
if (this.parent[x] !== x)Path compression to speed up find
if (rootX !== rootY)Union only if roots differ to avoid cycles
this.count--;Decrement count of connected components after union
if (connections.length < n - 1) return -1;Check if enough cables exist before unions
const uf = new UnionFind(n);Create union-find instance
for (const [u, v] of connections)Union connected computers
return uf.count - 1;Operations needed equals components minus one
TimeO(α(n) * connections.length)
SpaceO(n)
Union-find operations are almost O(1) amortized (α is inverse Ackermann function). We perform union for each connection.
💡 For n=100,000, this approach is very efficient and scales well.
Interview Verdict: Accepted
Union-find is the optimal approach for this problem and is preferred in interviews.