Check “connection status” for nodes in graphs
cycle
?
Union
connected
O(1)
time complexity// java
// LC 684
public class UnionFind3 {
// Union-find data structure
int[] parents;
int[] size;
int n;
// Constructor to initialize the union-find data structure
public UnionFind3(int[][] edges) {
HashSet<Integer> set = new HashSet<>();
for (int[] x : edges) {
.add(x[0]);
set.add(x[1]);
set}
this.n = set.size();
// Initialize parent and size arrays
this.parents = new int[n + 1]; // Using 1-based indexing
this.size = new int[n + 1];
for (int i = 1; i <= n; i++) {
this.parents[i] = i;
this.size[i] = 1;
}
}
// Find the root of the set containing 'x' with path compression
public int getParent(int x) {
/**
* NOTE !!!
*
* we use `!=` logic below to simplify code
*/
if (x != this.parents[x]) {
// Path compression: recursively find the parent and update the current node's
// parent
/**
* NOTE !!!
*
* we should update parent as `getParent(this.parents[x])`,
* e.g. -> use `this.parents[x]` as parameter, send into getParent method,
* -> then assign result to this.parents[x]
*
*/
this.parents[x] = getParent(this.parents[x]);
}
return this.parents[x];
}
// Union the sets containing x and y, return false if they are already connected
public boolean union(int x, int y) {
int rootX = getParent(x);
int rootY = getParent(y);
// If they are already in the same set, a cycle is detected
if (rootX == rootY) {
return false;
}
// Union by size: attach the smaller tree to the root of the larger tree
if (size[rootX] < size[rootY]) {
[rootX] = rootY;
parents[rootY] += size[rootX];
size} else {
[rootY] = rootX;
parents[rootX] += size[rootY];
size}
return true;
}
}
// Java
// LC 261
class UF {
int[] parents; // Parent array
public UF(int n) {
this.parents = new int[n];
for (int i = 0; i < n; i++) {
this.parents[i] = i; // Each node is its own parent initially
}
}
// Union operation
public boolean union(int a, int b) {
int aParent = find(a);
int bParent = find(b);
/**
* NOTE !!!
*
* If find(a) == find(b), it means they are already `CONNECTED`,
* -> `and adding the edge` would form a `CYCLE` (e.g. if we do `parents[aParent] = bParent`)
* -> so return `false` to prevent it.
*
* -> e.g. if `aParent == bParent`
* -> means node a, b ALREADY connected each other
* -> if we still go ahead and connect them in the other way
* -> will form a CYCLE, which is NOT a VALID TREE
*
* e.g.
*
* before
* a - c - b
*
*
* after
* a - c - b
* | ----- | (will form a cycle)
*
*/
/**
* Example
*
* # Detecting a Cycle in a Graph
*
* ## Example: Cycle Detection
*
* Given a graph with `n = 5` nodes and the following edges:
*
* ```
* edges = [[0,1], [1,2], [2,3], [1,3]]
* ```
*
* ### Step-by-Step Process
*
* 1. **Initial State**
* - Every node is its own parent:
* ```
* parents = [0, 1, 2, 3, 4]
* ```
*
* 2. **Processing Edges**
*
* - **Edge [0,1]**
* - `find(0) = 0`, `find(1) = 1` → Different sets, merge:
* ```
* parents = [1, 1, 2, 3, 4]
* ```
*
* - **Edge [1,2]**
* - `find(1) = 1`, `find(2) = 2` → Merge
* ```
* parents = [1, 2, 2, 3, 4]
* ```
*
* - **Edge [2,3]**
* - `find(2) = 2`, `find(3) = 3` → Merge
* ```
* parents = [1, 2, 3, 3, 4]
* ```
*
* - **Edge [1,3]**
* - `find(1) = 3`, `find(3) = 3` → Same parent (Cycle detected!)
* ```
* Cycle detected! Returning false.
* ```
*
* ### **Result**
* A **cycle is detected** in the graph, so the function returns **false**.
*/
if (aParent == bParent) {
return false; // Cycle detected
}
// Simple union without rank
[aParent] = bParent;
parentsreturn true;
}
// Find with path compression
public int find(int a) {
if (parents[a] != a) {
[a] = find(parents[a]); // Path compression
parents}
return parents[a];
}
}
#--------------------------
# UNION FIND V1 (basic)
#--------------------------
class UnionFind:
def __init__(self, n):
self.count = n
self.parent = [None] * n
self.size = [None] * n
for i in range(len(n)):
self.parent[i] = i
self.size = 1
def union(self, p, q):
= self.find(p)
rootP = self.find(q)
rootQ if rootP == rootQ:
return
#self.parent[rootQ] = rootP # this is OK as well
self.parent[rootP] = rootQ
self.count -= 1
def connected(self, p, q):
= self.find(p)
rootP = self.find(q)
rootQ return rootP == rootQ
def find(self, x):
while self.parent[x] != x:
= parent[x]
x return x
def count(self):
return self.count
#-------------------
# UNION FIND V2
#-------------------
class UnionFind(object):
def __init__(self, n):
self.set = range(n)
self.count = n
def find_set(self, x):
if self.set[x] != x:
self.set[x] = self.find_set(self.set[x]) # path compression.
return self.set[x]
def union_set(self, x, y):
= map(self.find_set, (x, y))
x_root, y_root if x_root != y_root:
self.set[min(x_root, y_root)] = max(x_root, y_root)
self.count -= 1
class Solution(object):
def countComponents(self, n, edges):
= UnionFind(n)
union_find for i, j in edges:
union_find.union_set(i, j)return union_find.count
# LC 130 Surrounded Regions
# NOTE : there is also bfs, dfs approaches
# V1
# IDEA : UNION FIND
# https://leetcode.com/problems/surrounded-regions/discuss/1764075/Python-or-Union-Find
class DSU:
def __init__(self, n):
self.root = list(range(n))
self.rank = [0]*n
def find(self, x):
if self.root[x] != x:
self.root[x] = self.find(self.root[x])
return self.root[x]
def union(self, x, y):
= self.find(x), self.find(y)
rx, ry
if rx == ry:
return False
if self.rank[rx] == self.rank[ry]:
self.rank[ry] += 1
if self.rank[rx] < self.rank[ry]:
self.root[rx] = ry
else:
self.root[ry] = rx
return True
class Solution:
def solve(self, board):
"""
Do not return anything, modify board in-place instead.
"""
#use union find to group the Os
#the Os on the edge has higher rank
#in the end, check each O cell, if the root of the cell is not in the edge, flip to x
= len(board), len(board[0])
m, n = DSU(m*n)
dsu = set()
edges for i in range(m):
for j in range(n):
if i == 0 or i == m - 1 or j == 0 or j == n - 1:
*i + j] = float("inf")
dsu.rank[n*i + j)
edges.add(n
for i in range(m):
for j in range(n):
if board[i][j] == "X": continue
for ni, nj in [(i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1)]:
if 0 <= ni < m and 0 <= nj < n and board[ni][nj] == "O":
*i + j, n*ni + nj)
dsu.union(n
for i in range(m):
for j in range(n):
if dsu.find(n*i + j) not in edges:
= "X"
board[i][j]
return board
# V1
# IDEA : UNION FIND
# https://leetcode.com/problems/surrounded-regions/discuss/1371795/python3-%2B-Union-Find
class Solution:
def solve(self, board):
"""
Do not return anything, modify board in-place instead.
"""
= {} #dic index : root
f
def find(x):
f.setdefault(x, x)if f[x] != x:
= find(f[x])
f[x] return f[x]
def union(x, y):
= find(x)
f[find(y)]
if not board or not board[0]:
return
= len(board)
row = len(board[0])
col = row * col
dummy #dummy is Point O colletion dont' need to be changed(need to be remained)
for i in range(row):
for j in range(col):
if board[i][j] == "O":
if i == 0 or i == row - 1 or j == 0 or j == col - 1:
* col + j, dummy)#need to be remained O
union(i else:
for x, y in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if board[i + x][j + y] == "O":#union all connected O
* col + j, (i + x) * col + (j + y))
union(i for i in range(row):
for j in range(col):
if find(dummy) == find(i * col + j):#Point O colletion dont' need to be changed
= "O"
board[i][j] else:
= "X" board[i][j]
# LC 261 Graph Valid Tree
// java
// LC 261 Graph Valid Tree
// V0'
// IDEA : QUICK FIND (gpt)
public boolean validTree_0(int n, int[][] edges) {
if (n == 0) {
return false;
}
/**
* Step 1) Initialize root array where each node is its own parent
*
* NOTE !!!
* we init an array with n length (NOT from edges)
*/
int[] root = new int[n];
for (int i = 0; i < n; i++) {
[i] = i;
root}
/**
* Step 2) update relation (union find)
*/
// Process each edge
for (int[] edge : edges) {
/**
* NOTE !!!
*
* find node "parent" with 2 perspective
* 1) from 1st element (e.g. edge[0])
* 2) from 2nd element (e.g. edge[1])
*
* so, if parent1 == parent2
* -> means there is a circular (because they have same parent, so nodes must "connect itself back" at some point),
* -> so input is NOT a valid tree
*/
int root1 = find(root, edge[0]); // parent1
int root2 = find(root, edge[1]); // parent2
// If the roots are the same, there's a cycle
if (root1 == root2) {
/** NOTE !!! if a cycle, return false directly */
return false;
} else {
// Union the sets
/** NOTE !!! if not a cycle, then "compress" the route, e.g. make node as the other node's parent */
[root1] = root2;
root}
}
/** Check if the number of edges is exactly n - 1 */
return edges.length == n - 1; // NOTE !!! this check
}
// Find function with path compression
private int find(int[] root, int e) {
if (root[e] == e) {
return e;
} else {
[e] = find(root, root[e]); // Path compression
rootreturn root[e];
}
}
# LC 886 Possible Bipartition
// java
// LC 399
// V4
// IDEA: UNION FIND (gpt)
class UnionFind {
private Map<String, String> parent;
private Map<String, Double> ratio;
public UnionFind() {
this.parent = new HashMap<>();
this.ratio = new HashMap<>();
}
// Finds the root of a node and applies path compression
public String find(String x) {
if (!parent.containsKey(x)) {
.put(x, x);
parent.put(x, 1.0);
ratio}
if (!x.equals(parent.get(x))) {
String originalParent = parent.get(x);
.put(x, find(originalParent));
parent.put(x, ratio.get(x) * ratio.get(originalParent));
ratio}
return parent.get(x);
}
// Union two nodes with the given value
public void union(String x, String y, double value) {
String rootX = find(x);
String rootY = find(y);
if (!rootX.equals(rootY)) {
.put(rootX, rootY);
parent.put(rootX, value * ratio.get(y) / ratio.get(x));
ratio}
}
// Get the ratio between two nodes if they are connected
public double isConnected(String x, String y) {
if (!parent.containsKey(x) || !parent.containsKey(y)) {
return -1.0;
}
String rootX = find(x);
String rootY = find(y);
if (!rootX.equals(rootY)) {
return -1.0;
}
return ratio.get(x) / ratio.get(y);
}
}
public double[] calcEquation_4(List<List<String>> equations, double[] values, List<List<String>> queries) {
= new UnionFind();
UnionFind uf
// Build the union-find structure
for (int i = 0; i < equations.size(); i++) {
String a = equations.get(i).get(0);
String b = equations.get(i).get(1);
double value = values[i];
.union(a, b, value);
uf}
// Process the queries
double[] results = new double[queries.size()];
for (int i = 0; i < queries.size(); i++) {
String c = queries.get(i).get(0);
String d = queries.get(i).get(1);
[i] = uf.isConnected(c, d);
results}
return results;
}
# LC 547 Friend Circles
# NOTE !! there are also BFS, DFS approaches
class UnionFind(object):
def __init__(self, n):
self.n = n
self.parent = [-1]*n
for i in range(n):
self.parent[i] = i
def find(self, i):
#Path Compression
if self.parent[i] != i:
self.parent[i] = self.find(self.parent[i])
return self.parent[i]
def union(self, x, y):
= self.find(x)
xroot = self.find(y)
yroot if xroot != yroot:
self.parent[yroot]= xroot
def get_count(self):
= set()
total print(self.parent)
for i in range(self.n):
self.find(i))
total.add(print("total", total)
return len(total)
class Solution:
def findCircleNum(self, M):
"""
:type M: List[List[int]]
:rtype: int
"""
#Union-Find Solution
= len(M[0])
n = UnionFind(n)
uf
for r in range(len(M)):
for c in range(len(M[0])):
if M[r][c] == 1:
uf.union(r,c)
return uf.get_count()
# LC 565. Array Nesting
# V0
# IDEA : UNION FIND
class Solution(object):
def arrayNesting(self, nums):
def search(idx):
= 0
cnt while nums[idx] >= 0:
+= 1
cnt next = nums[idx]
= -1
nums[idx] = next
idx return cnt
= 0
ans for x in range(len(nums)):
if nums[x] >= 0:
= max(ans, search(x))
ans return ans
# LC 737. Sentence Similarity II
# NOTE : there is also DFS, BFS approaches
# V0'''
# IDEA : UNION FIND
class Solution(object):
def areSentencesSimilarTwo(self, words1, words2, pairs):
if len(words1) != len(words2):
return False
= dict()
parent
def add(x):
if x not in parent:
= x
parent[x]
def find(x):
if x == parent[x]:
return x
= find(parent[x])
parent[x] return parent[x]
def union(x, y):
= find(x)
parentX = find(y)
parentY if parentX == parentY:
return
= parentX
parent[parentY]
for a, b in pairs:
add(a)
add(b)
union(a, b)
# print parent
for word1, word2 in zip(words1, words2):
# print word1, word2
if word1 == word2:
continue
if word1 not in parent or word2 not in parent:
return False
if find(word1) != find(word2):
return False
return True
# LC 323 Number of Connected Components in an Undirected Graph
# NOTE : there is ALSO dfs, bfs approaches
# V0
# IDEA : UNION FIND
# union find basic algorithm
class UnionFind:
def __init__(self, n):
self.n = n
self.parent = [x for x in range(n)]
def union(self, x, y):
#print (">>> union : x = {}, y = {}".format(x, y))
= self.find(x)
parentX = self.find(y)
parentY """
NOTE this !!!
-> if parentX == parentY, we DO NOTHING
"""
if parentX == parentY:
return
self.parent[parentX] = parentY
self.n -= 1
def find(self, x):
while x != self.parent[x]:
= self.parent[x]
x return x
def connected(self, x, y):
= self.find(x)
parentX = self.find(y)
parentY return parentX == parentY
def count(self):
return self.n
class Solution:
def countComponents(self, n, edges):
"""
build union find
step 1) init class (uf)
step 2) union all (a, b) in edges
step 3) return uf.count
"""
= UnionFind(n)
uf for a, b in edges:
uf.union(a, b)return uf.count()
# LC 1135. Connecting Cities With Minimum Cost
# note : there is also prime, Kruskal approaches
# V1
# IDEA : UNION FIND
# https://leetcode.com/problems/connecting-cities-with-minimum-cost/discuss/831263/Python-very-Concise-Union-Find
class Solution:
def minimumCost(self, N: int, connections: List[List[int]]) -> int:
= [x for x in range(N)]
parents
def find(x):
if parents[x] != x: parents[x] = find(parents[x])
return parents[x]
def union(u, v):
if find(u) == find(v): return False
= find(u)
parents[find(v)] return True
= lambda x: x[2])
connections.sort(key = 0
ans for u, v, val in connections:
if union(u-1, v-1): ans += val
return ans if len(set(find(x) for x in parents)) == 1 else -1