← Daily Logs
LeetCode 1584

Min Cost to Connect Points

MediumJul 26, 2026graphunion-findmstkruskal's-algorithm

Kruskal's algorithm turns out to be `redundantConnection`'s DSU loop with two changes: the input isn't already an edge list — every pair of points is a candidate edge, weighted by Manhattan distance, so the graph has to be built and sorted by cost before union-find can even start — and instead of stopping at the first cycle-forming edge, the loop keeps merging the cheapest edge that doesn't close one, summing cost until `n - 1` edges have connected everything.

The problem

Implement `minCostConnectPoints(points)` where `points[i] = [xi, yi]`. The cost to connect two points is their Manhattan distance, `|xi - xj| + |yi - yj|`. Return the minimum total cost to connect every point such that there's exactly one path between any pair — the minimum spanning tree over the complete graph of all pairwise Manhattan distances.

`points = [[0,0],[2,2],[3,3],[2,4],[4,2]]` returns `10`. Unlike every earlier graph entry, there's no `edges` input at all — every pair of points is a potential edge, and the question is which `n - 1` of the `n * (n - 1) / 2` possible ones make the cheapest tree.

The approach

There's no edge list to reuse this time — `points` gives coordinates, not connections, so every pair `(i, j)` becomes a candidate edge first, weighted by Manhattan distance, before Kruskal's algorithm (union-find, same as `redundantConnection`) can run at all. Sorting those candidate edges ascending by cost is what turns 'always take the cheapest edge that doesn't close a cycle' into a single linear pass instead of a search.

The `find`/union core — path compression, `parent[rootA] = rootB` — is unchanged from `redundantConnection`/`numberOfProvinces`/the Union-Find variant of `countComponents`; this is the fourth reuse of that exact primitive in this log, with the loop body changed a fourth way.

Where `redundantConnection` returned the instant it found an edge whose endpoints already shared a root — that was the whole answer — here it's the opposite edges, the ones whose endpoints *don't* share a root, that matter: each one is a genuine merge, so its cost is added to `totalCost` and `edgesUsed` increments. The loop stops as soon as `edgesUsed === n - 1`, since a spanning tree over `n` nodes never needs more edges than that.

The solution

js
class Solution {
    /**
     * @param {number[][]} points
     * @return {number}
     */
    minCostConnectPoints(points) {
        const n = points.length;
        const parent = Array.from({ length: n }, (_, i) => i);

        const find = (x) => {
            while (parent[x] !== x) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        };

        const edges = [];
        for (let i = 0; i < n; i++) {
            for (let j = i + 1; j < n; j++) {
                const cost = Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]);
                edges.push([cost, i, j]);
            }
        }
        edges.sort((a, b) => a[0] - b[0]);

        let totalCost = 0;
        let edgesUsed = 0;

        for (const [cost, a, b] of edges) {
            if (edgesUsed === n - 1) break;
            const rootA = find(a);
            const rootB = find(b);
            if (rootA !== rootB) {
                parent[rootA] = rootB;
                totalCost += cost;
                edgesUsed++;
            }
        }

        return totalCost;
    }
}

Time O(n^2 log n)Space O(n^2)

All entries