3613. Minimize Maximum Component Cost
Problem Description
You are given an undirected connected graph with n
nodes labeled from 0
to n - 1
and a 2D integer array edges
where edges[i] = [u_i, v_i, w_i]
denotes an undirected edge between node u_i
and node v_i
with weight w_i
, and an integer k
.
You are allowed to remove any number of edges from the graph such that the resulting graph has at most k
connected components.
The cost of a component is defined as the maximum edge weight in that component. If a component has no edges, its cost is 0.
Return the minimum possible value of the maximum cost among all components after such removals.
Intuition
The goal is to break the graph into at most k
connected components while minimizing the largest maximum edge weight in any component. Removing edges generally increases the number of components, and when a component is split, removing a large-weight edge will directly affect the cost in the resulting components.
If we sort all edges by their weights from smallest to largest, we can think of the problem as keeping the graph as connected as possible with the smallest possible maximum edge weight within each component. The fewer edges we keep, the more components we have.
Using a union-find data structure, we can build the graph by adding edges from smallest to largest weight. Each time an edge connects two components, the total number of components decreases. Once we have exactly k
components, the last edge's weight we added determines the maximum edge weight in any component. This is because all remaining edges are larger and can be removed. If k
equals n
, the graph can be fully disconnected into single nodes, so the maximum cost is 0
.
Solution Approach
The solution uses sorting and the union-find (disjoint set) data structure. Here’s how the approach works:
-
Special Case (
k == n
): Ifk
equalsn
, every node can become its own component, with no edges left. In this case, the cost of every component is0
, so we can directly return0
. -
Sort Edges: Sort
edges
based on their weights in ascending order. This ensures that lighter edges (with smaller weights) are considered first. Using smaller weighted edges to connect the components helps keep the maximum cost low. -
Union-Find Initialization: Prepare a union-find data structure by assigning each node as its own parent:
p = list(range(n))
The
find
function applies path compression to efficiently determine the parent of each node. -
Build Components by Connecting Edges: Start connecting nodes using the sorted edges. For each edge
[u, v, w]
, connectu
andv
if they are currently in different components. This merges two components into one.Every successful merge decreases the component count (
cnt -= 1
). Once the number of components is reduced tok
, the current edge's weight (w
) is the answer. This is because, from this point on, any further edges added (which are heavier) would not be needed, and removing them or not adding them won't increase the number of components beyondk
. -
Return the Result: Once
cnt == k
, return the weightw
of the current edge. This represents the minimum possible value for the maximum cost among all components.
Key patterns used:
- Greedy: Always connect components with the smallest possible edge to avoid unnecessarily high costs.
- Union-Find: Efficiently manage and merge connected components.
Example in code context:
edges.sort(key=lambda x: x[2])
p = list(range(n))
cnt = n
for u, v, w in edges:
pu, pv = find(u), find(v)
if pu != pv:
p[pu] = pv
cnt -= 1
if cnt <= k:
return w
This ensures the answer is found as soon as the number of components does not exceed k
. If cnt
never becomes k
or less (which shouldn't happen given the problem constraints), the fallback is returning 0
.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Suppose we have a graph with n = 5
nodes and the following edges:
edges = [ [0, 1, 2], [0, 2, 4], [1, 2, 3], [1, 3, 5], [2, 4, 6] ] k = 2
Objective: Remove any number of edges so that there are at most k = 2
connected components, and the largest maximum edge weight in any component is minimized.
Step 1. Sort Edges by Weight:
Sorted edges:
[ [0, 1, 2], [1, 2, 3], [0, 2, 4], [1, 3, 5], [2, 4, 6] ]
Step 2. Initialize Union-Find:
Each node is its own component:
p = [0, 1, 2, 3, 4]
Number of components: cnt = 5
Step 3. Iterate Through Edges:
- Add edge [0, 1, 2]:
- Connect 0 and 1.
- Components: 4
- Add edge [1, 2, 3]:
- Connect (0-1) and 2.
- Components: 3
- Add edge [0, 2, 4]:
- 0 and 2 are already connected. Skip.
- Add edge [1, 3, 5]:
- Connect (0-1-2) and 3.
- Components: 2
At this point (cnt = k = 2
), the last edge added had a weight of 5
.
Step 4. Return the Result:
The answer is 5
, which is the minimum possible value of the maximum cost among all components after breaking the graph into at most 2 connected components.
Alternatives:
- Removing heavier edges first could result in a greater maximum cost.
- If
k = 3
, the process would stop after adding edge [1, 2, 3], with the answer being3
.
Summary Table:
Action | Edge Added | Components Left | Current Max Edge Weight |
---|---|---|---|
Initial | - | 5 | - |
Add [0, 1, 2] | Yes | 4 | 2 |
Add [1, 2, 3] | Yes | 3 | 3 |
Add [0, 2, 4] | No | 3 | - |
Add [1, 3, 5] | Yes | 2 | 5 |
Final Components:
- One component: {0, 1, 2, 3} (max edge 5)
- One component: {4} (no edge, cost 0)
Result: Minimum possible value of the maximum cost among all components is 5.
Solution Implementation
1class Solution:
2 def minCost(self, n: int, edges: list[list[int]], k: int) -> int:
3 # Disjoint Set Union (Union Find) find function with path compression
4 def find(x: int) -> int:
5 if parent[x] != x:
6 parent[x] = find(parent[x])
7 return parent[x]
8
9 # Special case: already k components, no cost needed
10 if k == n:
11 return 0
12
13 # Sort edges by their weight
14 edges.sort(key=lambda edge: edge[2])
15
16 # Initialize number of components and parent array
17 components = n
18 parent = list(range(n))
19
20 # Kruskal's approach: connect components until we have k components left
21 for u, v, weight in edges:
22 root_u = find(u)
23 root_v = find(v)
24 if root_u != root_v:
25 parent[root_u] = root_v # Union operation
26 components -= 1 # Reduce number of components
27 if components <= k: # Stop when we reach k components
28 return weight
29
30 # If we never reach k components, return 0 as default (may depend on problem constraints)
31 return 0
32
1class Solution {
2 // Array to keep track of parent in Union-Find structure
3 private int[] parent;
4
5 /**
6 * Finds the minimum cost needed so that the graph has no more than k connected components.
7 * @param n Number of nodes in the graph
8 * @param edges Each edge represented as {from, to, weight}
9 * @param k Maximum number of connected components allowed
10 * @return The minimum weight at which the graph can be separated into at most k components
11 */
12 public int minCost(int n, int[][] edges, int k) {
13 if (k == n) {
14 // If k equals the number of nodes, each node is its own component -- cost is 0
15 return 0;
16 }
17
18 // Initialize Union-Find parent array: each node is its own parent
19 parent = new int[n];
20 Arrays.setAll(parent, i -> i);
21
22 // Sort the edges based on their weights in ascending order
23 Arrays.sort(edges, Comparator.comparingInt(edge -> edge[2]));
24
25 int componentCount = n; // Start with n components
26
27 // Iterate through edges in ascending order of weight
28 for (int[] edge : edges) {
29 int nodeU = edge[0], nodeV = edge[1], weight = edge[2];
30 int parentU = find(nodeU), parentV = find(nodeV);
31
32 // If nodes are from different components, union them
33 if (parentU != parentV) {
34 parent[parentU] = parentV; // Union operation
35 componentCount--;
36
37 // If the number of components is less than or equal to k, return this weight
38 if (componentCount <= k) {
39 return weight;
40 }
41 }
42 }
43
44 // If no such division is possible, return 0
45 return 0;
46 }
47
48 /**
49 * Union-Find 'find' function with path compression.
50 * @param x Node to find the root of.
51 * @return Root parent of x.
52 */
53 private int find(int x) {
54 if (parent[x] != x) {
55 parent[x] = find(parent[x]); // Path compression
56 }
57 return parent[x];
58 }
59}
60
1#include <vector>
2#include <algorithm>
3#include <numeric>
4
5class Solution {
6public:
7 int minCost(int n, std::vector<std::vector<int>>& edges, int k) {
8 // If the number of groups required equals number of nodes, no edges needed.
9 if (k == n) {
10 return 0;
11 }
12
13 // Parent array for Disjoint Set Union (DSU)
14 std::vector<int> parent(n);
15 std::iota(parent.begin(), parent.end(), 0); // Initialize parent[i] = i
16
17 // Sort edges by cost ascending (edges[i] = {u, v, cost})
18 std::sort(edges.begin(), edges.end(),
19 [](const std::vector<int>& a, const std::vector<int>& b) {
20 return a[2] < b[2];
21 });
22
23 // Find function with path compression
24 std::function<int(int)> find = [&](int x) {
25 if (parent[x] != x) {
26 parent[x] = find(parent[x]);
27 }
28 return parent[x];
29 };
30
31 int components = n; // Start with n disjoint components
32
33 // Iterate over all edges and join the components
34 for (const auto& edge : edges) {
35 int u = edge[0];
36 int v = edge[1];
37 int cost = edge[2];
38 int parentU = find(u);
39 int parentV = find(v);
40
41 if (parentU != parentV) {
42 parent[parentU] = parentV; // Union two sets
43 --components; // One less component after union
44
45 // If the number of components is reduced to k, return the current edge's cost
46 if (components <= k) {
47 return cost;
48 }
49 }
50 }
51
52 // If not enough edges were used to achieve k components, return 0
53 return 0;
54 }
55};
56
1function minCost(n: number, edges: number[][], k: number): number {
2 // Union-Find parent array
3 const parent: number[] = Array.from({ length: n }, (_, i) => i);
4
5 // Find with path compression
6 function find(x: number): number {
7 if (parent[x] !== x) {
8 parent[x] = find(parent[x]);
9 }
10 return parent[x];
11 }
12
13 // If every node needs to be its own component, cost is 0
14 if (k === n) {
15 return 0;
16 }
17
18 // Sort the edges by weight in ascending order
19 edges.sort((a, b) => a[2] - b[2]);
20
21 // Count of current components
22 let count = n;
23
24 // Kruskal's algorithm to connect components
25 for (const [u, v, weight] of edges) {
26 const rootU = find(u);
27 const rootV = find(v);
28 // If the roots are different, connect them
29 if (rootU !== rootV) {
30 parent[rootU] = rootV;
31 count--;
32 // When the number of components reduces to k, return current edge weight
33 if (count <= k) {
34 return weight;
35 }
36 }
37 }
38 // If not enough components were merged, the cost is 0
39 return 0;
40}
41
Time and Space Complexity
-
Time Complexity: The dominant operations are sorting the
edges
list and performing union-find operations.- Sorting the
edges
takesO(m \log m)
, wherem
is the number of edges. - Each union-find operation with path compression is nearly
O(1)
amortized, and at mostO(m)
such operations occur. So, the overall time complexity isO(m \log m)
. If the graph is connected and dense,m
can be up toO(n^2)
, but usuallym
is the input size.
- Sorting the
-
Space Complexity: The space complexity comes from the parent array
p
of sizen
, so it isO(n)
.
Thus, compared to the reference answer which states O(n \log n)
time, the compiled analysis is:
- Time complexity:
O(m \log m)
(wherem
is number of edges) - Space complexity:
O(n)
(wheren
is number of nodes)
How many times is a tree node visited in a depth first search?
Recommended Readings
Union Find Disjoint Set Union Data Structure Introduction Prerequisite Depth First Search Review problems dfs_intro Once we have a strong grasp of recursion and Depth First Search we can now introduce Disjoint Set Union DSU This data structure is motivated by the following problem Suppose we have sets of elements
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Want a Structured Path to Master System Design Too? Don’t Miss This!