LeetCode 1584 - Min Cost to Connect All Points
This problem asks us to connect all given points on a 2D plane with the minimum possible total cost. Each point is repre
Difficulty: 🟡 Medium
Topics: Array, Union-Find, Graph Theory, Minimum Spanning Tree
Solution
Problem Understanding
This problem asks us to connect all given points on a 2D plane with the minimum possible total cost. Each point is represented as a pair of coordinates [x, y]. The cost of connecting two points is defined as their Manhattan distance:
$$|x_i - x_j| + |y_i - y_j|$$
The goal is to ensure that every point becomes connected, either directly or indirectly, while minimizing the total connection cost.
The statement also says that there must be exactly one simple path between every pair of points. This condition describes a tree structure. In graph theory, a connected graph with no cycles and n - 1 edges is called a spanning tree. Since we want the minimum total edge cost, this becomes a classic Minimum Spanning Tree problem.
We can think of the problem as building a complete weighted graph:
- Every point is a node
- Every pair of points has an edge
- The edge weight is the Manhattan distance between those points
Then we must find the Minimum Spanning Tree of this graph.
The constraints are important:
- Up to
1000points - Coordinates can be large, both positive and negative
- All points are distinct
A complete graph with 1000 points contains:
$$\frac{1000 \times 999}{2} = 499500$$
possible edges. This is large, but still manageable for efficient MST algorithms such as Prim's or Kruskal's algorithm.
Several edge cases are important:
- A single point requires no connections, so the answer is
0 - Points may have negative coordinates, so absolute differences must be handled carefully
- Multiple edges can have the same weight
- A naive recursive graph traversal could become inefficient if cycles are not managed correctly
- Generating all edges explicitly may consume significant memory if not handled carefully
Approaches
Brute Force Approach
The brute force idea is to generate every possible way to connect points and then choose the valid connected structure with minimum total cost.
For n points, there are exponentially many subsets of edges. We would need to check whether each subset forms a valid spanning tree and then compute its total weight. This quickly becomes computationally impossible.
Another less extreme brute force method is:
- Generate all pairwise edges
- Sort them
- Try combinations of edges until finding the minimum valid spanning tree
Even this becomes infeasible because the number of edge combinations grows explosively.
Although brute force guarantees correctness by exhaustively exploring possibilities, it cannot handle 1000 points within reasonable time limits.
Optimal Approach
The key observation is that this is exactly a Minimum Spanning Tree problem.
A Minimum Spanning Tree connects all nodes:
- Using exactly
n - 1edges - Without cycles
- With minimum total edge weight
Two classic algorithms solve MST problems efficiently:
- Kruskal's Algorithm
- Prim's Algorithm
For this problem, Prim's Algorithm is especially attractive because we do not need to explicitly store all edges. Since the graph is complete, storing all pairwise edges would require nearly 500,000 edges.
Instead, Prim's Algorithm incrementally grows the tree:
- Start from one point
- Repeatedly connect the nearest unvisited point
- Keep track of the minimum known connection cost for each point
This avoids explicitly constructing the full graph while still achieving the correct minimum cost.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | Exponential | Exponential | Tries all possible connection combinations |
| Optimal, Prim's Algorithm | O(n²) | O(n) | Efficient MST construction without storing all edges |
Algorithm Walkthrough
We use Prim's Algorithm to build the Minimum Spanning Tree.
Step 1: Initialize tracking structures
We maintain three main pieces of information:
visited[i], whether pointihas already been added to the MSTmin_distance[i], the cheapest known cost to connect pointitotal_cost, the running MST cost
Initially:
- Start from point
0 - Set its connection cost to
0 - Set every other point's cost to infinity
This means point 0 is chosen as the starting node.
Step 2: Repeatedly select the cheapest unvisited point
At every iteration:
- Find the unvisited point with the smallest connection cost
- Add it to the MST
- Add its cost to
total_cost
This greedy step is the core of Prim's Algorithm.
The selected point is guaranteed to be the cheapest valid expansion of the current tree.
Step 3: Update neighboring connection costs
After adding a new point to the MST:
- Compute its Manhattan distance to every unvisited point
- If this new distance is smaller than the currently known minimum cost, update it
This step continuously maintains the cheapest possible connection into the growing tree.
Step 4: Continue until all points are connected
We repeat the process exactly n times.
After all points are visited:
- Every point belongs to the MST
- Exactly
n - 1connections were effectively chosen - The total cost is minimized
Why it works
Prim's Algorithm works because of the cut property of Minimum Spanning Trees.
At every step, the algorithm chooses the minimum weight edge connecting the current tree to an unvisited node. This greedy choice is always safe because any MST must contain the cheapest edge crossing such a cut.
By repeatedly applying this rule, the algorithm constructs a globally optimal Minimum Spanning Tree.
Python Solution
from typing import List
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) -> int:
n = len(points)
if n == 1:
return 0
visited = [False] * n
min_distance = [float("inf")] * n
min_distance[0] = 0
total_cost = 0
for _ in range(n):
current = -1
for i in range(n):
if not visited[i]:
if current == -1 or min_distance[i] < min_distance[current]:
current = i
visited[current] = True
total_cost += min_distance[current]
x1, y1 = points[current]
for neighbor in range(n):
if not visited[neighbor]:
x2, y2 = points[neighbor]
distance = abs(x1 - x2) + abs(y1 - y2)
if distance < min_distance[neighbor]:
min_distance[neighbor] = distance
return total_cost
The implementation directly follows Prim's Algorithm.
The visited array tracks which points are already part of the MST. This prevents cycles and ensures each point is added exactly once.
The min_distance array stores the minimum known connection cost for every point. Initially, all values are infinity except the starting point, which has cost 0.
Inside the outer loop, we search for the unvisited point with the smallest connection cost. This point becomes the next MST node.
After selecting the point, we compute Manhattan distances from it to every remaining unvisited point. If a newly discovered connection is cheaper than the previously known cost, we update min_distance.
At the end of the process, total_cost contains the weight of the Minimum Spanning Tree.
Go Solution
package main
import "math"
func minCostConnectPoints(points [][]int) int {
n := len(points)
if n == 1 {
return 0
}
visited := make([]bool, n)
minDistance := make([]int, n)
for i := 0; i < n; i++ {
minDistance[i] = math.MaxInt32
}
minDistance[0] = 0
totalCost := 0
for i := 0; i < n; i++ {
current := -1
for j := 0; j < n; j++ {
if !visited[j] {
if current == -1 || minDistance[j] < minDistance[current] {
current = j
}
}
}
visited[current] = true
totalCost += minDistance[current]
x1, y1 := points[current][0], points[current][1]
for neighbor := 0; neighbor < n; neighbor++ {
if !visited[neighbor] {
x2, y2 := points[neighbor][0], points[neighbor][1]
distance := abs(x1-x2) + abs(y1-y2)
if distance < minDistance[neighbor] {
minDistance[neighbor] = distance
}
}
}
}
return totalCost
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
The Go implementation mirrors the Python solution closely.
Go does not provide a built in absolute value function for integers, so we define a helper abs function.
Slices are used instead of Python lists. The math.MaxInt32 constant initializes distances to a very large value.
Since Go integers are large enough for the given constraints, integer overflow is not an issue here.
Worked Examples
Example 1
Input:
points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
Initial State
| Point | Min Distance | Visited |
|---|---|---|
| 0 | 0 | False |
| 1 | inf | False |
| 2 | inf | False |
| 3 | inf | False |
| 4 | inf | False |
Total cost = 0
Iteration 1
Choose point 0.
Distances from point 0:
| To | Distance |
|---|---|
| 1 | 4 |
| 2 | 13 |
| 3 | 7 |
| 4 | 7 |
Updated state:
| Point | Min Distance | Visited |
|---|---|---|
| 0 | 0 | True |
| 1 | 4 | False |
| 2 | 13 | False |
| 3 | 7 | False |
| 4 | 7 | False |
Total cost = 0
Iteration 2
Choose point 1 with cost 4.
Distances from point 1:
| To | Distance |
|---|---|
| 2 | 9 |
| 3 | 3 |
| 4 | 7 |
Updated state:
| Point | Min Distance | Visited |
|---|---|---|
| 0 | 0 | True |
| 1 | 4 | True |
| 2 | 9 | False |
| 3 | 3 | False |
| 4 | 7 | False |
Total cost = 4
Iteration 3
Choose point 3 with cost 3.
Distances from point 3:
| To | Distance |
|---|---|
| 2 | 10 |
| 4 | 4 |
Updated state:
| Point | Min Distance | Visited |
|---|---|---|
| 0 | 0 | True |
| 1 | 4 | True |
| 2 | 9 | False |
| 3 | 3 | True |
| 4 | 4 | False |
Total cost = 7
Iteration 4
Choose point 4 with cost 4.
Distance update:
| To | Distance |
|---|---|
| 2 | 14 |
No improvement for point 2.
Total cost = 11
Iteration 5
Choose point 2 with cost 9.
Final total cost:
20
Example 2
Input:
points = [[3,12],[-2,5],[-4,1]]
Initial State
| Point | Min Distance | Visited |
|---|---|---|
| 0 | 0 | False |
| 1 | inf | False |
| 2 | inf | False |
Iteration 1
Choose point 0.
Distances:
| To | Distance |
|---|---|
| 1 | 12 |
| 2 | 18 |
Iteration 2
Choose point 1.
Distances:
| To | Distance |
|---|---|
| 2 | 6 |
Update point 2 from 18 to 6.
Iteration 3
Choose point 2.
Total cost:
0 + 12 + 6 = 18
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n²) | Each iteration scans all points and updates all distances |
| Space | O(n) | Arrays for visited state and minimum distances |
The algorithm runs n iterations. In each iteration:
- We scan all points to find the next minimum node
- We scan all points again to update distances
This gives:
$$O(n \times n) = O(n^2)$$
The space usage is linear because we only maintain a few arrays of size n.
Test Cases
from typing import List
class Solution:
def minCostConnectPoints(self, points: List[List[int]]) -> int:
n = len(points)
if n == 1:
return 0
visited = [False] * n
min_distance = [float("inf")] * n
min_distance[0] = 0
total_cost = 0
for _ in range(n):
current = -1
for i in range(n):
if not visited[i]:
if current == -1 or min_distance[i] < min_distance[current]:
current = i
visited[current] = True
total_cost += min_distance[current]
x1, y1 = points[current]
for neighbor in range(n):
if not visited[neighbor]:
x2, y2 = points[neighbor]
distance = abs(x1 - x2) + abs(y1 - y2)
if distance < min_distance[neighbor]:
min_distance[neighbor] = distance
return total_cost
solution = Solution()
assert solution.minCostConnectPoints(
[[0,0],[2,2],[3,10],[5,2],[7,0]]
) == 20 # provided example 1
assert solution.minCostConnectPoints(
[[3,12],[-2,5],[-4,1]]
) == 18 # provided example 2
assert solution.minCostConnectPoints(
[[0,0]]
) == 0 # single point
assert solution.minCostConnectPoints(
[[0,0],[1,1]]
) == 2 # simple two-point connection
assert solution.minCostConnectPoints(
[[-1,-1],[1,1]]
) == 4 # negative coordinates
assert solution.minCostConnectPoints(
[[0,0],[2,0],[4,0],[6,0]]
) == 6 # points on straight line
assert solution.minCostConnectPoints(
[[0,0],[0,0],[1,1]]
) == 2 # duplicate coordinates scenario if allowed
assert solution.minCostConnectPoints(
[[1000000,1000000],[-1000000,-1000000]]
) == 4000000 # large coordinate values
| Test | Why |
|---|---|
[[0,0],[2,2],[3,10],[5,2],[7,0]] |
Validates standard MST construction |
[[3,12],[-2,5],[-4,1]] |
Tests negative coordinates |
[[0,0]] |
Single node edge case |
[[0,0],[1,1]] |
Smallest nontrivial graph |
[[-1,-1],[1,1]] |
Confirms absolute value handling |
[[0,0],[2,0],[4,0],[6,0]] |
Linear structure |
[[0,0],[0,0],[1,1]] |
Tests duplicate coordinates handling |
[[1000000,1000000],[-1000000,-1000000]] |
Large coordinate stress test |
Edge Cases
Single Point
If the input contains only one point, no edges are required because the graph is already connected.
This case can easily cause issues if the algorithm assumes at least one edge exists. The implementation handles this by immediately returning 0 when n == 1.
Negative Coordinates
Coordinates may be negative, so Manhattan distance calculations must use absolute values correctly.
An incorrect implementation might accidentally subtract coordinates directly without applying abs, producing invalid negative distances. Both solutions explicitly compute:
$$|x_1 - x_2| + |y_1 - y_2|$$
which guarantees correctness regardless of coordinate signs.
Large Coordinate Values
Coordinates may be as large as 10^6.
This means Manhattan distances can become quite large. An implementation using small integer types could overflow. Python integers automatically expand as needed, and Go's standard int type safely handles these values on LeetCode platforms.
Dense Complete Graph
Every point can connect to every other point, producing nearly 500,000 edges for the maximum input size.
A naive adjacency list storing all edges would consume unnecessary memory. The chosen Prim's Algorithm implementation computes distances on demand instead of storing the entire graph, keeping space complexity linear.