🧠
Union-Find with Path Compression and Union by Rank Optimization
💡 This approach improves the previous union-find by adding union by rank to keep trees shallow, further optimizing performance for very large inputs.
Intuition
Use union-find with path compression and union by rank to minimize tree height, speeding up find and union operations.
Algorithm
- Initialize union-find with parent and rank arrays.
- For each stone, union its row and column nodes with offset.
- Use path compression in find to flatten trees.
- Use union by rank to attach smaller tree under larger tree.
- Return total stones minus number of connected components.
💡 The key is maintaining rank to keep union operations efficient and path compression to speed up finds.
class UnionFind:
def __init__(self):
self.parent = {}
self.rank = {}
self.count = 0
def find(self, x):
if x not in self.parent:
self.parent[x] = x
self.rank[x] = 0
self.count += 1
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 removeStones(stones):
uf = UnionFind()
OFFSET = 10000
for x, y in stones:
uf.union(x, y + OFFSET)
return len(stones) - uf.count
# Driver code
if __name__ == '__main__':
stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
print(removeStones(stones)) # Output: 5
Line Notes
self.rank = {}Initialize rank dictionary to track tree height
self.rank[x] = 0New node starts with rank 0
if self.rank[rootX] < self.rank[rootY]Attach smaller rank tree under larger rank tree
self.rank[rootX] += 1Increment rank when two equal rank trees merge
self.parent[x] = self.find(self.parent[x])Path compression to flatten tree
self.count -= 1Decrease count when union merges two sets
uf.union(x, y + OFFSET)Union row and column with offset
return len(stones) - uf.countCalculate max stones removed
import java.util.*;
class UnionFind {
private Map<Integer, Integer> parent = new HashMap<>();
private Map<Integer, Integer> rank = new HashMap<>();
private int count = 0;
public int find(int x) {
if (!parent.containsKey(x)) {
parent.put(x, x);
rank.put(x, 0);
count++;
}
if (parent.get(x) != x) {
parent.put(x, find(parent.get(x)));
}
return parent.get(x);
}
public void union(int x, int y) {
int rootX = find(x);
int rootY = find(y);
if (rootX != rootY) {
int rankX = rank.get(rootX);
int rankY = rank.get(rootY);
if (rankX < rankY) {
parent.put(rootX, rootY);
} else if (rankX > rankY) {
parent.put(rootY, rootX);
} else {
parent.put(rootY, rootX);
rank.put(rootX, rankX + 1);
}
count--;
}
}
public int getCount() {
return count;
}
}
public class Solution {
public int removeStones(int[][] stones) {
UnionFind uf = new UnionFind();
int OFFSET = 10000;
for (int[] stone : stones) {
uf.union(stone[0], stone[1] + OFFSET);
}
return stones.length - uf.getCount();
}
public static void main(String[] args) {
Solution sol = new Solution();
int[][] stones = {{0,0},{0,1},{1,0},{1,2},{2,1},{2,2}};
System.out.println(sol.removeStones(stones)); // Output: 5
}
}
Line Notes
private Map<Integer, Integer> rank = new HashMap<>()Rank map to track tree height
rank.put(x, 0)Initialize rank for new node
if (rankX < rankY)Attach smaller rank tree under larger rank tree
rank.put(rootX, rankX + 1)Increment rank when merging equal rank trees
parent.put(x, find(parent.get(x)))Path compression optimization
count--Decrease count when union merges sets
uf.union(stone[0], stone[1] + OFFSET)Union row and column with offset
return stones.length - uf.getCount()Calculate max stones removed
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
class UnionFind {
unordered_map<int, int> parent;
unordered_map<int, int> rank;
int count = 0;
public:
int find(int x) {
if (parent.find(x) == parent.end()) {
parent[x] = x;
rank[x] = 0;
count++;
}
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int x, int y) {
int rootX = find(x);
int 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--;
}
}
int getCount() {
return count;
}
};
class Solution {
public:
int removeStones(vector<vector<int>>& stones) {
UnionFind uf;
const int OFFSET = 10000;
for (auto& stone : stones) {
uf.unite(stone[0], stone[1] + OFFSET);
}
return stones.size() - uf.getCount();
}
};
int main() {
Solution sol;
vector<vector<int>> stones = {{0,0},{0,1},{1,0},{1,2},{2,1},{2,2}};
cout << sol.removeStones(stones) << endl; // Output: 5
return 0;
}
Line Notes
unordered_map<int, int> rankRank map to track tree height
rank[x] = 0Initialize rank for new node
if (rank[rootX] < rank[rootY])Attach smaller rank tree under larger rank tree
rank[rootX]++Increment rank when merging equal rank trees
parent[x] = find(parent[x])Path compression optimization
count--Decrease count when union merges sets
uf.unite(stone[0], stone[1] + OFFSET)Union row and column with offset
return stones.size() - uf.getCount()Calculate max stones removed
class UnionFind {
constructor() {
this.parent = new Map();
this.rank = new Map();
this.count = 0;
}
find(x) {
if (!this.parent.has(x)) {
this.parent.set(x, x);
this.rank.set(x, 0);
this.count++;
}
if (this.parent.get(x) !== x) {
this.parent.set(x, this.find(this.parent.get(x)));
}
return this.parent.get(x);
}
union(x, y) {
const rootX = this.find(x);
const rootY = this.find(y);
if (rootX !== rootY) {
const rankX = this.rank.get(rootX);
const rankY = this.rank.get(rootY);
if (rankX < rankY) {
this.parent.set(rootX, rootY);
} else if (rankX > rankY) {
this.parent.set(rootY, rootX);
} else {
this.parent.set(rootY, rootX);
this.rank.set(rootX, rankX + 1);
}
this.count--;
}
}
}
function removeStones(stones) {
const uf = new UnionFind();
const OFFSET = 10000;
for (const [x, y] of stones) {
uf.union(x, y + OFFSET);
}
return stones.length - uf.count;
}
// Driver code
const stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]];
console.log(removeStones(stones)); // Output: 5
Line Notes
this.rank = new Map()Rank map to track tree height
this.rank.set(x, 0)Initialize rank for new node
if (rankX < rankY)Attach smaller rank tree under larger rank tree
this.rank.set(rootX, rankX + 1)Increment rank when merging equal rank trees
this.parent.set(x, this.find(this.parent.get(x)))Path compression optimization
this.count--Decrease count when union merges sets
uf.union(x, y + OFFSET)Union row and column with offset
return stones.length - uf.countCalculate max stones removed
Union-find with rank and path compression has near constant amortized time per operation.
💡 This is the most optimized union-find approach, suitable for very large inputs.
Interview Verdict: Accepted / Optimal with best union-find optimizations
This approach is the best practice for union-find problems in interviews.