The cheapest node in each group
An undirected graph has n nodes numbered 0 to n − 1 and a list of
edges. Node i has the value values[i], which may be negative. For each connected
group of nodes take its smallest value, and return the sum of those minimums.
Think of the groups as networks of offices that share a supplier: each network needs one supplier, and the cost of supplying everyone is the sum of the cheapest quote in each network.
Examples
| Input | Output | Why |
|---|---|---|
n = 8, edges = [[0,1],[1,2],[0,2],[3,4],[5,6],[3,7]], values = [7,3,9,4,8,6,2,5] | 9 | Groups {0,1,2}, {3,4,7} and {5,6}: minimums 3 + 4 + 2. |
n = 3, edges = [], values = [5,1,4] | 10 | Every node is its own group. |
n = 4, edges = [[0,1],[1,2],[2,3]], values = [3,-2,8,0] | -2 | One group; its minimum is negative. |
Constraints
- 1 ≤ n ≤ 10⁴
- 0 ≤ edges.length ≤ 2 · 10⁴
- −10⁶ ≤ values[i] ≤ 10⁶
Hints
Hint 1
Find the connected components: every unvisited node starts a search that marks its whole group.
Hint 2
While a search runs, keep the smallest value it has seen.
Hint 3
When the search finishes, add that minimum to the answer.
Where people lose marks
- Initialising each group's minimum to 0, which is wrong when values can be negative and when all are positive.
- Adding every node's value rather than one minimum per group.
- Forgetting isolated nodes: they are groups of one.
Dry run on 8,[[0,1],[1,2],[0,2],[3,4],[5,6],[3,7]],[7,3,9,4,8,6,2,5]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 3groups1
Node 0 is not in any group yet: start a search from it. It reaches 0, 1, 2: group 1, size 3, smallest value 3.
-
Step 2 / 3groups2
Node 3 is not in any group yet: start a search from it. It reaches 3, 4, 7: group 2, size 3, smallest value 4.
-
Step 3 / 3groups3
Node 5 is not in any group yet: start a search from it. It reaches 5, 6: group 3, size 2, smallest value 2. Every node belongs to a group: 3 in all. The sum of the group minimums is 3 + 4 + 2 = 9.
How to think about it
This is the components template with f = minimum over a group and g = sum across groups. Scan the nodes; each unvisited node starts a search, which tracks the smallest value among the nodes it marks. When the search ends, add that minimum to the total.
A minimum's starting point must be the first node's value (or plus infinity), not zero: with negative values, zero would be wrong.
Complexity. Time O(n + m) · Space O(n + m)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def sum_of_group_minimums(n, edges, values):
adj = [[] for _ in range(n)]
for a, b in edges:
adj[a].append(b)
adj[b].append(a)
seen = [False] * n
total = 0
for s in range(n):
if seen[s]:
continue
seen[s] = True
stack, smallest = [s], values[s] # f = min over the group
while stack:
u = stack.pop()
smallest = min(smallest, values[u])
for v in adj[u]:
if not seen[v]:
seen[v] = True
stack.append(v)
total += smallest # g = sum across groups
return total#include <algorithm>
#include <vector>
long long solve(int n, const std::vector<std::vector<int>>& edges, const std::vector<int>& values) {
std::vector<std::vector<int>> adj(n);
for (const auto& e : edges) { adj[e[0]].push_back(e[1]); adj[e[1]].push_back(e[0]); }
std::vector<bool> seen(n, false);
long long total = 0;
for (int s = 0; s < n; ++s) {
if (seen[s]) continue;
seen[s] = true;
std::vector<int> stack{s};
int smallest = values[s]; // min over this group
while (!stack.empty()) {
int u = stack.back();
stack.pop_back();
smallest = std::min(smallest, values[u]);
for (int v : adj[u])
if (!seen[v]) { seen[v] = true; stack.push_back(v); }
}
total += smallest; // sum across groups
}
return total;
}import java.util.*;
class Solution {
public long sumOfGroupMinimums(int n, int[][] edges, int[] values) {
List<List<Integer>> adj = new ArrayList<>();
for (int i = 0; i < n; i++) adj.add(new ArrayList<>());
for (int[] e : edges) { adj.get(e[0]).add(e[1]); adj.get(e[1]).add(e[0]); }
boolean[] seen = new boolean[n];
long total = 0;
for (int s = 0; s < n; s++) {
if (seen[s]) continue;
seen[s] = true;
ArrayDeque<Integer> stack = new ArrayDeque<>();
stack.push(s);
int smallest = values[s]; // min over this group
while (!stack.isEmpty()) {
int u = stack.pop();
smallest = Math.min(smallest, values[u]);
for (int v : adj.get(u))
if (!seen[v]) { seen[v] = true; stack.push(v); }
}
total += smallest; // sum across groups
}
return total;
}
}function sumOfGroupMinimums(n, edges, values) {
const adj = Array.from({ length: n }, () => []);
for (const [a, b] of edges) { adj[a].push(b); adj[b].push(a); }
const seen = new Array(n).fill(false);
let total = 0;
for (let s = 0; s < n; s++) {
if (seen[s]) continue;
seen[s] = true;
const stack = [s];
let smallest = values[s]; // min over this group
while (stack.length) {
const u = stack.pop();
smallest = Math.min(smallest, values[u]);
for (const v of adj[u]) if (!seen[v]) { seen[v] = true; stack.push(v); }
}
total += smallest; // sum across groups
}
return total;
}fn solve(n: i64, edges: &[[i64; 2]], values: &[i64]) -> i64 {
let n = n as usize;
let mut adj = vec![Vec::new(); n];
for e in edges {
let (a, b) = (e[0] as usize, e[1] as usize);
adj[a].push(b);
adj[b].push(a);
}
let mut seen = vec![false; n];
let mut total = 0;
for s in 0..n {
if seen[s] {
continue;
}
seen[s] = true;
let mut stack = vec![s];
let mut smallest = values[s]; // min over this group
while let Some(u) = stack.pop() {
smallest = smallest.min(values[u]);
for &v in &adj[u] {
if !seen[v] {
seen[v] = true;
stack.push(v);
}
}
}
total += smallest; // sum across groups
}
total
}import qualified Data.Map.Strict as M
import qualified Data.Set as S
solve :: Int -> [[Int]] -> [Int] -> Int
solve n edges values = go [0 .. n - 1] S.empty 0
where
adj = M.fromListWith (++) (concat [[(a, [b]), (b, [a])] | [a, b] <- edges])
val = M.fromList (zip [0 ..] values)
go [] _ total = total
go (s : rest) seen total
| S.member s seen = go rest seen total
| otherwise =
let group = flood [s] (S.singleton s)
in go rest (S.union seen group) (total + minimum [val M.! v | v <- S.toList group])
flood [] acc = acc
flood (u : st) acc =
let new = [v | v <- M.findWithDefault [] u adj, not (S.member v acc)]
in flood (new ++ st) (foldr S.insert acc new)
Variants to try
- Return the minimum together with which node achieves it, for each group.
- Edges arrive over time: report the sum after each new edge (union-find keeping a minimum at each root).
- Each group needs its two cheapest suppliers: keep the two smallest values per group.
Next problems
Count the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Medium15 minThe largest island
Return the number of cells in the largest island of a 0/1 grid: count each component's cells and keep the maximum.
Open the problem → Medium15 minCount the friend groups
People know each other directly or through friends. Count the separate groups, given an n × n matrix of who knows whom.
Open the problem →Write a solution and run it against the real test table.
Keep going
Count the islands in a grid
Every unvisited land cell starts a new island; flood it so it is never counted again. The first graph problem most interviews ask.
Open the problem → Medium15 minThe largest island
Return the number of cells in the largest island of a 0/1 grid: count each component's cells and keep the maximum.
Open the problem → Medium15 minCount the friend groups
People know each other directly or through friends. Count the separate groups, given an n × n matrix of who knows whom.
Open the problem → GraphsThe pattern behind it
Grids, networks and dependencies are all graphs. Breadth-first search finds shortest paths, depth-first search and union-find find what is connected, Dijkstra handles weighted edges, and a queue of nodes with no remaining prerequisites gives an order.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.