Unit 11 Session 2 Advanced (Click for link to problem statements)
Understand what the interviewer is asking for by using test cases and questions about the problem.
- Established a set (2-3) of test cases to verify their own solution later.
- Established a set (1-2) of edge cases to verify their solution handles complexities.
- Have fully understood the problem and have no clarifying questions.
- Have you verified any Time/Space Constraints for this problem?
-1
.HAPPY CASE
Input: connections = [[1, 2, 5], [1, 3, 6], [2, 3, 1]], n = 3
Output: 6
Explanation: The minimum cost to connect all camps is 6 (camp 1 to 2 for 5, and camp 2 to 3 for 1).
EDGE CASE
Input: connections = [[1, 2, 3], [3, 4, 4]], n = 4
Output: -1
Explanation: Camps 1 and 4 cannot be connected to the other camps.
Match what this problem looks like to known categories of problems, e.g. Linked List or Dynamic Programming, and strategies or patterns in those categories.
For Minimum Spanning Tree problems, we want to consider the following approaches:
Plan the solution with appropriate visualizations and pseudocode.
General Idea: Use Kruskal’s Algorithm to construct the minimum spanning tree. First, sort the edges by cost and use Union-Find to connect the camps while ensuring no cycles. If we manage to connect all camps, return the total cost; otherwise, return -1
.
1) Sort all the connections by cost in ascending order.
2) Initialize a Union-Find structure to track connected components.
3) Iterate through the sorted connections, and for each one:
a) If the two camps are not already connected, union them.
b) Add the connection cost to the total cost.
c) Stop early if we have connected all camps with `n - 1` edges.
4) If all camps are connected, return the total cost. Otherwise, return `-1`.
⚠️ Common Mistakes
n-1
edges have been used).Implement the code to solve the algorithm.
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
self.rank = [1] * size
self.components = size # Number of connected components
def find(self, p):
if self.parent[p] != p:
self.parent[p] = self.find(self.parent[p]) # Path compression
return self.parent[p]
def union(self, p, q):
rootP = self.find(p)
rootQ = self.find(q)
if rootP != rootQ:
# Union by rank
if self.rank[rootP] > self.rank[rootQ]:
self.parent[rootQ] = rootP
elif self.rank[rootP] < self.rank[rootQ]:
self.parent[rootP] = rootQ
else:
self.parent[rootQ] = rootP
self.rank[rootP] += 1
self.components -= 1 # Reduce the number of components when we unite two
def connected(self):
return self.components == 1
def min_rebuilding_cost(n, connections):
# Sort the connections by the cost in ascending order
connections.sort(key=lambda x: x[2])
# Initialize Union-Find for n camps (indexed from 1 to n, so size is n+1)
uf = UnionFind(n + 1) # We use 1-based indexing for camps
total_cost = 0
edges_used = 0
# Iterate over each connection in ascending order of cost
for x, y, cost in connections:
# Union the two camps if they are not already connected
if uf.find(x) != uf.find(y):
uf.union(x, y)
total_cost += cost
edges_used += 1
# If we've used n-1 edges, we can stop early (MST complete)
if edges_used == n - 1:
break
# Check if all camps are connected
if edges_used == n - 1:
return total_cost
else:
return -1 # Not all camps are connected
Review the code by running specific example(s) and recording values (watchlist) of your code's variables along the way.
Example 1:
Evaluate the performance of your algorithm and state any strong/weak or future potential work.
Assume E
is the number of edges and V
is the number of nodes (camps).
O(E log E + E log V)
due to sorting the edges and using Union-Find operations.O(V)
for storing the parent and rank arrays in Union-Find.