medium

Connecting Cities With Minimum Cost

Find the minimum cost to connect every city together given the cost of each possible connection.

1. Define the problem

Connecting Cities With Minimum Cost

There are n cities labeled from 1 to n, and connections is an array where connectionsi = [city1, city2, cost] means it costs cost to connect city1 and city2 directly. Return the minimum cost to connect all n cities using Prim's algorithm ; if some city is unreachable from the others, return -1. Grow a tree outward starting at city 1, tracking the frontier — the cheapest known edge from the tree to each city not yet in it — in a min-heap , and always absorb the cheapest frontier edge next.

Constraints

  • 1 ≤ n ≤ 104
  • 0 ≤ connections.length ≤ 104
  • connectionsi.length == 3
  • 1 ≤ city1, city2 ≤ n
  • 0 ≤ cost ≤ 105
  • city1 != city2

Example

Inputn = 3, connections = [[1, 2, 5], [1, 3, 6], [2, 3, 1]]
Output6

Explanation Absorb 1→2 for cost 5, which reveals 2→3 for cost 1 — cheaper than the old 1→3 edge (cost 6). Total cost 5 + 1 = 6.

2. Know the words first

In plain terms

Minimum spanning tree
A set of edges that connects every node using the smallest possible total edge weight, with no cycles.
Frontier edge
The cheapest known edge connecting the tree built so far to a node that isn't in the tree yet.
3. Visualize the solution

Grow the tree from city 1, absorbing the cheapest frontier edge

Grow the tree from city 1, absorbing the cheapest frontier edge
Statusinit

Start the tree at city 1. Frontier: 1→2 costs 5, 1→3 costs 6 — the cheapest frontier edge is 1→2.

What happens in this step

tree = {1}
frontier: 1→2 (w=5), 1→3 (w=6)

cheapest frontier edge is 1→2 (w=5) — absorb it next.
Step 1 of 3

Steps to visualize

  1. Start the tree at city 1 and add its edges to the frontier.
  2. Always absorb the cheapest frontier edge next — that grows the tree by exactly one city.
  3. Absorbing a city adds its edges to the frontier, replacing a costlier entry whenever a cheaper way in appears.
  4. Stop once every city is in the tree; the sum of absorbed edges is the minimum cost, or -1 if a city is never reached.
4. Walk through the code

Walk through the code

Same walkthrough, now with the code. Press Next to move one step and watch which lines run.

Grow the tree from city 1, absorbing the cheapest frontier edge
Statusinit

Start the tree at city 1. Frontier: 1→2 costs 5, 1→3 costs 6 — the cheapest frontier edge is 1→2.

What happens in this step

tree = {1}
frontier: 1→2 (w=5), 1→3 (w=6)

cheapest frontier edge is 1→2 (w=5) — absorb it next.
Step 1 of 3
5. Solution

Solution

solution.tsTypeScript
function minimumCost(n, connections) {
  const adjacency = new Map();
  for (let city = 1; city <= n; city++) adjacency.set(city, []);
  for (const [a, b, cost] of connections) {
    adjacency.get(a).push([b, cost]);
    adjacency.get(b).push([a, cost]);
  }

  function heapPush(heap, item) {
    heap.push(item);
    let i = heap.length - 1;
    while (i > 0) {
      const parent = (i - 1) >> 1;
      if (heap[parent][0] <= heap[i][0]) break;
      [heap[parent], heap[i]] = [heap[i], heap[parent]];
      i = parent;
    }
  }

  function heapPop(heap) {
    const top = heap[0];
    const last = heap.pop();
    if (heap.length > 0) {
      heap[0] = last;
      let i = 0;
      while (true) {
        const left = 2 * i + 1;
        const right = 2 * i + 2;
        let smallest = i;
        if (left < heap.length && heap[left][0] < heap[smallest][0]) smallest = left;
        if (right < heap.length && heap[right][0] < heap[smallest][0]) smallest = right;
        if (smallest === i) break;
        [heap[smallest], heap[i]] = [heap[i], heap[smallest]];
        i = smallest;
      }
    }
    return top;
  }

  const visited = new Set([1]);
  const frontier = [];
  for (const [neighbor, weight] of adjacency.get(1)) {
    heapPush(frontier, [weight, neighbor]);
  }

  let totalCost = 0;
  let edgesUsed = 0;

  while (frontier.length > 0 && edgesUsed < n - 1) {
    const [weight, node] = heapPop(frontier);
    if (visited.has(node)) continue;
    visited.add(node);
    totalCost += weight;
    edgesUsed++;
    for (const [neighbor, edgeWeight] of adjacency.get(node)) {
      if (!visited.has(neighbor)) heapPush(frontier, [edgeWeight, neighbor]);
    }
  }

  return visited.size === n ? totalCost : -1;
}
Time
O(E log V)
Space
O(V + E)
6. Test cases

Test cases

InputExpectedCovers
n = 3, connections = [[1, 2, 5], [1, 3, 6], [2, 3, 1]]6example from the docstring
n = 4, connections = [[1, 2, 3], [3, 4, 4]]-1two components can never be joined into one tree
n = 1, connections = []0a single city needs no connections at all
n = 2, connections = [[1, 2, 10]]10smallest non-trivial case, exactly one connection
n = 4, connections = [[1, 2, 1], [2, 3, 1], [3, 4, 1], [1, 4, 10], [1, 3, 10]]3the frontier prefers a cheap chain over pricier direct shortcuts
n = 3, connections = [[1, 2, 1], [1, 3, 1], [2, 3, 100]]2an expensive edge that would only create a cycle is never absorbed
n = 5, connections = [[1, 2, 2], [1, 3, 2], [1, 4, 2], [1, 5, 2], [2, 3, 1]]7a mid-tree edge undercuts a direct connection after the frontier updates