Create parent and rank arrays for nodes 1 to 3. Each node is initially its own parent with rank 0.
💡 This sets up the union-find structure where each node starts as a separate set.
Line:parent = list(range(n+1))
rank = [0]*(n+1)
💡 Each node is isolated, so no cycles exist yet.
traverse
Process first edge [1,2]: find roots
Find the root parents of nodes 1 and 2. Both are their own parents.
💡 Finding roots tells us which sets nodes belong to before union.
Line:rootA = find(1)
rootB = find(2)
💡 Roots differ, so no cycle is formed by this edge.
union
Union sets of nodes 1 and 2
Since roots differ, attach the tree with lower rank under the higher rank tree. Both ranks equal, so attach 2 under 1 and increment rank of 1.
💡 Union merges sets to keep track of connected components.
Line:parent[rootB] = rootA
rank[rootA] += 1
💡 Union by rank keeps tree shallow, improving find efficiency.
traverse
Process second edge [1,3]: find roots
Find root parents of nodes 1 and 3. Node 1's root is 1; node 3 is its own root.
💡 Finding roots checks if nodes are already connected.
Line:rootA = find(1)
rootB = find(3)
💡 Roots differ, so no cycle is formed by this edge.
union
Union sets of nodes 1 and 3
Roots differ; attach root 3 under root 1. Rank of root 1 remains 1.
💡 Union merges the sets to maintain connectivity information.
Line:parent[rootB] = rootA
💡 Union by rank avoids increasing tree height unnecessarily.
traverse
Process third edge [2,3]: find roots
Find root parents of nodes 2 and 3. Both have root 1 after path compression.
💡 Finding roots detects if adding this edge creates a cycle.
Line:rootA = find(2)
rootB = find(3)
💡 Roots are the same, indicating a cycle if this edge is added.
compare
Detect cycle on edge [2,3]
Since roots of 2 and 3 are the same, adding edge [2,3] creates a cycle. This edge is redundant.
💡 Detecting same root means the edge closes a cycle.
Line:if rootA == rootB:
return [u, v]
💡 Union-find efficiently detects cycles by root comparison.
reconstruct
Return redundant edge [2,3]
The algorithm returns the edge [2,3] as the redundant connection that forms a cycle.
💡 Returning this edge solves the problem by identifying the cycle-causing edge.
Line:return [u, v]
💡 The first edge that connects nodes in the same set is the redundant connection.
reconstruct
Final state: redundant edge identified
The algorithm finishes with the redundant edge [2,3] identified, completing the cycle detection.
💡 This final state confirms the cycle edge found by union-find.
💡 The union-find structure efficiently detects cycles by tracking connected components.
summary
Summary: cycle detection complete
The algorithm has identified the redundant edge [2,3] that creates a cycle, and no further edges remain to process.
💡 This final step confirms the algorithm's completion and the correctness of the detected cycle edge.
💡 Understanding the final state helps solidify how union-find detects cycles efficiently.
def findRedundantConnection(edges):
n = len(edges)
parent = list(range(n+1)) # STEP 1: Initialize parent array
rank = [0]*(n+1) # STEP 1: Initialize rank array
def find(x):
if parent[x] != x:
parent[x] = find(parent[x]) # STEP 2,6: Path compression during find
return parent[x]
def union(a, b):
rootA = find(a) # STEP 2,4,6: Find root of a
rootB = find(b) # STEP 2,4,6: Find root of b
if rootA == rootB:
return False # STEP 7: Cycle detected
if rank[rootA] < rank[rootB]:
parent[rootA] = rootB # STEP 3,5: Attach lower rank tree under higher
elif rank[rootB] < rank[rootA]:
parent[rootB] = rootA
else:
parent[rootB] = rootA
rank[rootA] += 1 # STEP 3: Increment rank when ranks equal
return True
for u, v in edges:
if not union(u, v): # STEP 7: Detect and return redundant edge
return [u, v]
# Driver code
if __name__ == '__main__':
edges = [[1,2],[1,3],[2,3]]
print(findRedundantConnection(edges)) # Output: [2,3]
📊
Redundant Connection (Find Cycle Edge) - Watch the Algorithm Execute, Step by Step
Watching each union and find operation visually reveals how cycles form and why the union-find structure efficiently detects them.
Step 1/10
·Active fill★Answer cell
Visiting: none
1
2
3
Queue / Stack
1-21-32-3
Visiting: 1
Edge: 1 → 2
1
2
3
Queue / Stack
1-21-32-3
Visiting: none
Edge: 1 → 2
1
2
3
Queue / Stack
1-32-3
Visiting: 1
Edge: 1 → 3
1
2
3
Queue / Stack
1-32-3
Visiting: none
Edge: 1 → 3
1
2
3
Queue / Stack
2-3
Visiting: 2
Edge: 2 → 3
1
2
3
Queue / Stack
2-3
Visiting: none
Edge: 2 → 3
1
2
3
Visiting: none
Edge: 2 → 3
1
2
3
Visiting: none
1
2
3
Visiting: none
1
2
3
Key Takeaways
✓ Union-Find detects cycles by checking if two nodes share the same root before union.
This insight is hard to see from code alone because it requires understanding the dynamic structure of sets.
✓ Union by rank keeps the tree shallow, improving efficiency of find operations.
Visualizing rank changes clarifies why union-find is efficient.
✓ Path compression flattens the tree during find, speeding up future queries.
Seeing parent updates during find reveals how path compression optimizes the structure.
Practice
(1/5)
1. You are given a graph with edges of three types: edges usable by Alice only, edges usable by Bob only, and edges usable by both. The goal is to remove the maximum number of edges while ensuring both Alice and Bob can still traverse the entire graph. Which algorithmic approach guarantees an optimal solution for this problem?
easy
A. A greedy approach that processes type 3 edges first using union-find, then type 1 and 2 edges separately.
B. A brute force approach that tries all subsets of edges and checks connectivity for both Alice and Bob.
C. A dynamic programming approach that counts ways to connect nodes with subsets of edges.
D. A shortest path algorithm like Dijkstra's applied separately for Alice and Bob.
Solution
Step 1: Understand problem constraints
The problem requires maximizing edge removals while keeping the graph fully traversable for both Alice and Bob, who share some edges (type 3) and have exclusive edges (type 1 and 2).
Step 2: Identify the best approach
Processing type 3 edges first with union-find maximizes shared connectivity, then adding exclusive edges ensures full connectivity for both. This greedy union-find approach guarantees optimality, unlike brute force (too slow), DP (not applicable), or shortest path (irrelevant).
Final Answer:
Option A -> Option A
Quick Check:
Greedy union-find with type 3 edges first -> optimal [OK]
Hint: Process shared edges first to maximize connectivity [OK]
Common Mistakes:
Trying brute force despite exponential complexity
Ignoring type 3 edges first
Using shortest path algorithms incorrectly
2. Examine the following buggy code snippet for the optimal solution. Which line contains the subtle bug that can cause incorrect results?
medium
A. Line 2: Sorting edges by type descending
B. Line 16: Checking if both Alice and Bob are fully connected
C. Line 9: Using union-find union for type 3 edges
D. Line 6: Not adjusting node indices from 1-based to 0-based
Solution
Step 1: Identify indexing issue
Input edges use 1-based node indices, but union-find expects 0-based. Missing adjustment causes wrong unions or runtime errors.
Step 2: Confirm other lines are correct
Sorting edges, union calls, and connectivity checks are correct; only indexing is buggy.
Hint: Check if node indices are adjusted before union-find calls [OK]
Common Mistakes:
Ignoring 1-based to 0-based index conversion
Misusing union-find union return value
Skipping connectivity check for both users
3. What is the time complexity of the optimal union-find approach for the Swim in Rising Water problem on an n x n grid, where n is the grid dimension?
medium
A. O(n^2 log n) due to sorting all n^2 cells and near-constant union-find operations
B. O(n^4) due to nested loops over grid and union operations
C. O(n^2 * maxElevation) because of simulating water rise for each elevation
D. O(n^2) because each cell is processed once with union-find operations
Solution
Step 1: Identify main operations
Sorting all n^2 cells takes O(n^2 log n^2) = O(n^2 log n). Union-find operations are almost O(1) amortized per union/find.
Step 2: Combine complexities
Total complexity dominated by sorting: O(n^2 log n). Union-find merges add negligible overhead.
Final Answer:
Option A -> Option A
Quick Check:
Sorting dominates; union-find nearly constant time per operation [OK]
Hint: Sorting n^2 cells dominates complexity [OK]
Common Mistakes:
Assuming union-find operations cost O(log n) each
Confusing brute force simulation complexity with optimal
Ignoring sorting cost or treating union-find as O(1) without justification
4. Suppose you want to find a minimum spanning forest (MST for each connected component) in a graph that may be disconnected. Which modification to the Kruskal's algorithm with union-find is necessary to correctly handle this scenario?
hard
A. Remove the break condition and process all edges to connect all components separately
B. Use DFS to find connected components first, then run Kruskal on each separately
C. Stop after adding n-1 edges, as usual, since MST covers all nodes
D. Add edges only if they connect nodes in the same component to avoid cycles
Solution
Step 1: Understand MST vs MST forest
In disconnected graphs, MST is a forest; stopping after n-1 edges assumes connectivity, which may not hold.
Step 2: Modify Kruskal to process all edges
Removing the break condition allows union-find to connect all components separately, building MST forest.
Final Answer:
Option A -> Option A
Quick Check:
Process all edges to cover disconnected components [OK]
Hint: Don't stop early if graph is disconnected; build MST forest instead [OK]
Common Mistakes:
Stopping early assuming graph is connected
Running DFS first adds unnecessary complexity
Misusing union-find to connect nodes in same component
5. Suppose the problem is modified so that each connection can be used multiple times (i.e., cables are reusable), and you want to find the minimum number of operations to connect all computers. Which modification to the algorithm is correct?
hard
A. Check if the number of connections is at least n-1; if yes, return components - 1, else return -1.
B. Use a BFS or DFS to find the minimum spanning tree since reuse allows cycles.
C. Count connected components and return zero if at least one connection exists, since reuse removes cable limits.
D. No change needed; the original union-find solution still applies directly.
Solution
Step 1: Understand reuse impact
Reusing cables means the only constraint is having enough cables to connect all nodes, i.e., at least n-1 connections.
Step 2: Apply union-find with check
We still count connected components and need at least n-1 cables; if so, minimum operations = components - 1; else -1.
Step 3: Reject naive assumptions
No change needed; the original union-find solution still applies directly. ignores cable count; Use a BFS or DFS to find the minimum spanning tree since reuse allows cycles. is unnecessary; Count connected components and return zero if at least one connection exists, since reuse removes cable limits. incorrectly assumes any connection suffices.
Final Answer:
Option A -> Option A
Quick Check:
Minimum cables needed remains n-1 even with reuse [OK]