Start learning free

LESSON 480 OF 536 · GRAPHS

Min Cost to Connect All Points in Java: Kruskal with Union-Find

Practice O(n2 log n) Read the full explanation

You are given an array points of n points on a grid, where points[i] = [xi, yi].

Connecting two points [xi, yi] and [xj, yj] costs their Manhattan distance, |xi - xj| + |yi - yj|: the distance along the grid lines, the way a taxi drives through city blocks.

Return the minimum total cost to connect all the points. The points are all connected when you can get from any point to any other by following connections.

 

Example 1:

Input: points = [[0, 0], [2, 2], [3, 10], [5, 2], [7, 0]]
Output: 20
Explanation: Four connections join the five points for the least total cost:
    [0, 0] - [2, 2] costs 2 + 2 = 4
    [2, 2] - [5, 2] costs 3 + 0 = 3
    [5, 2] - [7, 0] costs 2 + 2 = 4
    [2, 2] - [3, 10] costs 1 + 8 = 9
The total is 4 + 3 + 4 + 9 = 20.

Example 2:

Input: points = [[3, 12], [-2, 5], [-4, 1]]
Output: 18
Explanation: [-2, 5] - [-4, 1] costs 2 + 4 = 6 and [3, 12] - [-2, 5] costs 5 + 7 = 12,
18 in all. Connecting [3, 12] to [-4, 1] instead would cost 7 + 11 = 18 on its own.

Example 3:

Input: points = [[0, 0], [1, 0], [0, 1], [10, 10]]
Output: 21
Explanation: The two cheapest connections are [0, 0] - [1, 0] and [0, 0] - [0, 1], 1 each.
The next cheapest, [1, 0] - [0, 1] (cost 2), joins two points that are already connected
through [0, 0], so it would only add cost. Then [10, 10] joins through [1, 0], at cost
9 + 10 = 19. The total is 1 + 1 + 19 = 21.

 

Constraints:

  • 1 <= points.length <= 1000
  • -106 <= xi, yi <= 106
  • All the points are distinct.

Note:

Your algorithm should run in O(n2 log n) time and use O(n2) extra space.

The tutorial uses Kruskal's algorithm. Think of every pair of points as an edge of a graph, whose cost is the two points' Manhattan distance: there are n · (n - 1) / 2 edges. The cheapest way to connect all the points is a minimum spanning tree: n - 1 edges that connect every point, with the least total cost. Kruskal's algorithm sorts the edges from the cheapest up and takes each edge whose two ends are still in different groups, which joins the two groups into one. An edge whose ends are already in the same group would only close a cycle, so it is skipped. Union-find, as in Number of Connected Components in an Undirected Graph, tells in almost constant time whether two points are in the same group. After n - 1 edges every point is in one group, and the algorithm can stop.

Taking the cheapest edge between two groups is always safe: the finished tree has to join those two groups somehow, and if it used a dearer edge there, swapping in the cheaper one would give a tree that costs no more. Sorting the n2 edges takes O(n2 log n) time, and keeping them takes O(n2) space.

Prim's algorithm is the other classic way: it grows a single tree from one point, each time adding the cheapest edge from the tree to a point outside it, with a heap as in Dijkstra's algorithm or, on a graph where every pair is an edge like this one, with an O(n2) scan.

STEP 1 OF 32
    Ctrl + Enter to check

    JAVA · GRAPHS

    Min Cost To Connect All Points in Java, explained

    Problem overview

    The method minCostConnectPoints(int[][] points) receives n distinct points [x, y]. Connecting two points costs their Manhattan distance, |xi - xj| + |yi - yj|: the distance along the grid lines, as a taxi drives through city blocks. The method returns the minimum total cost so that you can get from any point to any other by following connections.

    For [[0,0],[2,2],[3,10],[5,2],[7,0]] the answer is 20. Four connections join the five points: [0,0]-[2,2] costs 4, [2,2]-[5,2] costs 3, [5,2]-[7,0] costs 4 and [2,2]-[3,10] costs 9. The total is 4 + 3 + 4 + 9 = 20. For [[3,12],[-2,5],[-4,1]] the answer is 18.

    Approach: Kruskal's algorithm

    Think of every pair of points as an edge of a graph, with the Manhattan distance as its cost. There are n · (n - 1) / 2 edges. The cheapest way to connect all points is a minimum spanning tree: n - 1 edges that join every point, with the least total cost.

    Kruskal's algorithm sorts the edges from cheapest up and takes each edge whose two ends are still in different groups, which merges the two groups into one. An edge whose ends are already in the same group would only close a cycle, so it is skipped. Taking the cheapest edge between two groups is always safe: the finished tree has to join those two groups somehow, and if it used a dearer edge there, swapping in the cheaper one would give a tree that costs no more.

    To ask "are these two points in the same group?" quickly, the solution uses union-find. Each group is a tree in which every point links to a parent, and the root, the only point that is its own parent, names the group. Two points are in the same group exactly when they lead to the same root.

    Take [[0,0],[1,0],[0,1],[10,10]]. Sorted by cost, the edges as [cost, i, j] come out as:

    [1, 0, 1]
    [1, 0, 2]
    [2, 1, 2]
    [19, 1, 3]
    [19, 2, 3]
    [20, 0, 3]
    

    The first two edges join points 0, 1 and 2, at cost 1 each. The edge [2, 1, 2] links two points already in one group, so it is skipped. The edge [19, 1, 3] brings in point 3. That makes three edges, n - 1, and the total is 1 + 1 + 19 = 21.

    The sort of about n²/2 edges takes O(n² log n) time, and storing them takes O(n²) space, which is what the statement allows for n up to 1000. Prim's algorithm, which grows one tree from a single point, is the other classic way to build a minimum spanning tree.

    Step by step

    1. Store the number of points in n.
    2. Create edges as a List<int[]>, an ArrayList. Each edge is an array {cost, i, j}. The cost comes first because the edges get sorted by it.
    3. List every pair once. The outer loop runs i from 0 to n - 1, and the inner loop runs j from i + 1. Starting at i + 1 never pairs a point with itself and lists each pair once, because connecting i to j costs the same as j to i.
    4. Compute the cost as Math.abs of the x difference plus Math.abs of the y difference, then add new int[]{cost, i, j} to edges.
    5. Sort the edges by cost with edges.sort and a lambda that returns first[0] - second[0]. A negative result puts first in front. A cost is at most 4,000,000, so the subtraction can't overflow.
    6. Set up union-find. Create parent and size, both int arrays of length n. parent[x] is the parent of point x, and size[r] counts the points in the group rooted at r.
    7. Start every point alone. A loop sets parent[i] = i and size[i] = 1. New int arrays hold zeros, so the sizes must be set.
    8. Create total and used, both 0: the cost of the edges taken and how many edges the network has.
    9. Walk the sorted edges. For each edge, find the root of edge[1] in rootA and of edge[2] in rootB, using the helper find, written at the end.
    10. Skip same-group edges. If rootA == rootB, the edge would close a cycle and only add cost.
    11. Merge by size. Otherwise, if size[rootA] < size[rootB], swap the two roots using a temporary variable, so rootA is the root of the larger group. Then set parent[rootB] = rootA and add size[rootB] to size[rootA]. The smaller tree always goes under the larger one, which keeps the trees shallow.
    12. Take the edge. Add edge[0] to total and do used++.
    13. Stop early. Once used == n - 1, all points are in one group, so return total. The edges left could only close cycles.
    14. After the loop, return total. The loop gets there only for a single point: there are no edges and nothing to pay.
    15. Write find. It walks up while parent[x] != x. Inside the loop, parent[x] = parent[parent[x]] makes x skip to its grandparent (path halving), then x = parent[x] climbs. The loop ends at the root, which is returned.

    The solution in Java

    class Solution {
        public int minCostConnectPoints(int[][] points) {
            int n = points.length;
            List<int[]> edges = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    int cost = Math.abs(points[i][0] - points[j][0]) + Math.abs(points[i][1] - points[j][1]);
                    edges.add(new int[]{cost, i, j});
                }
            }
            edges.sort((first, second) -> first[0] - second[0]);
            int[] parent = new int[n];
            int[] size = new int[n];
            for (int i = 0; i < n; i++) {
                parent[i] = i;
                size[i] = 1;
            }
            int total = 0;
            int used = 0;
            for (int[] edge : edges) {
                int rootA = find(parent, edge[1]);
                int rootB = find(parent, edge[2]);
                if (rootA != rootB) {
                    if (size[rootA] < size[rootB]) {
                        int temp = rootA;
                        rootA = rootB;
                        rootB = temp;
                    }
                    parent[rootB] = rootA;
                    size[rootA] += size[rootB];
                    total += edge[0];
                    used++;
                    if (used == n - 1) {
                        return total;
                    }
                }
            }
            return total;
        }
    
        int find(int[] parent, int x) {
            while (parent[x] != x) {
                parent[x] = parent[parent[x]];
                x = parent[x];
            }
            return x;
        }
    }
    

    The Java details worth a closer look:

    • Arrays are passed by reference. find receives the very array parent, so when it changes an entry, minCostConnectPoints sees the change. The parameter x is a copy of a number: assigning to it inside find changes only the local copy.
    • The comparator lambda. (first, second) -> first[0] - second[0] is a Comparator<int[]>. Subtracting is safe only because the costs are small: Integer.MAX_VALUE - (-5) wraps around to -2147483644, so with huge values you would use Integer.compare(first[0], second[0]) instead.
    • Swapping needs a temporary variable. Assigning rootA = rootB first would lose the old rootA, so it is saved in temp.
    • A loop instead of recursion. find uses a while loop, so a long chain of parents can't overflow the call stack.
    • Calling find from an instance method. find is an instance method in the same class, so the call find(parent, edge[1]) needs no object name.
    • int costs. One cost is at most 4,000,000, so it fits easily in an int: two points at (-1000000, -1000000) and (1000000, 1000000) cost 4000000. The running total is an int too, which suits a minimum tree: it uses only the cheapest connections, not every edge.

    Complexity

    Time: O(n² log n). Building the edges takes O(n²). Sorting about n²/2 edges takes O(n² log n). The union-find calls cost almost constant time each, thanks to union by size and path halving.

    Space: O(n²). The edge list holds n · (n - 1) / 2 small arrays. parent and size add only O(n).

    Edge cases and common mistakes

    • One point. [[5,5]] returns 0: there are no edges, the loop doesn't run and total is 0.
    • Two points. [[0,0],[3,4]] returns 7, the Manhattan distance 3 + 4, not the straight-line distance 5.
    • Negative coordinates. Differences are wrapped in Math.abs, so [[3,12],[-2,5],[-4,1]] gives 18.
    • Taking a cycle edge. In [[0,0],[1,0],[0,1],[10,10]] the edge between [1,0] and [0,1] costs 2 but joins two points already connected through [0,0]. Taking it would give 23 instead of 21.
    • Counting an edge without merging. Adding the cost but forgetting to set parent[rootB] leaves the groups apart, and the next edges are judged wrongly.
    • Using parent[x] as the root. A point's parent is not its root unless it is the root itself, so always call find.

    Testing

    • [[0,0],[2,2],[3,10],[5,2],[7,0]] gives 20.
    • [[3,12],[-2,5],[-4,1]] gives 18.
    • [[0,0],[1,0],[0,1],[10,10]] gives 21.
    • [[5,5]] gives 0.
    • [[0,0],[3,4]] gives 7.
    • [[-1000000,-1000000],[1000000,1000000]] gives 4000000.
    Keep going with Java500+ step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free