LeetCode 1595 - Minimum Cost to Connect Two Groups of Points
The problem is asking us to find the minimum cost to connect two groups of points, where the first group has size1 point
Difficulty: 🔴 Hard
Topics: Array, Dynamic Programming, Bit Manipulation, Matrix, Bitmask
Solution
Problem Understanding
The problem is asking us to find the minimum cost to connect two groups of points, where the first group has size1 points and the second group has size2 points. We are given a cost matrix cost of size size1 x size2 where cost[i][j] represents the cost of connecting the i-th point in the first group to the j-th point in the second group.
A valid connection means every point in the first group is connected to at least one point in the second group, and every point in the second group is connected to at least one point in the first group. Connections can be multiple for a single point, so there is no restriction on connecting one point to multiple points in the other group. The goal is to find the configuration of connections that minimizes the total cost.
The constraints are small enough for dynamic programming: size1 and size2 are at most 12, which suggests bitmask DP is feasible since 2^12 = 4096. size1 >= size2 tells us we can iterate efficiently over the smaller group's subsets. The cost[i][j] values range from 0 to 100, which is manageable for summing.
Important edge cases include when size1 or size2 is 1, when all costs are equal, and when some costs are 0. These cases could affect the correctness of a naive greedy solution.
Approaches
The brute-force approach would be to generate all possible ways to connect points from the first group to the second group such that all points are connected. For each configuration, we sum the costs and select the minimum. This is correct but infeasible because the number of combinations grows exponentially: each point in the first group has 2^size2 - 1 valid ways to connect to the second group, leading to (2^size2 - 1)^size1 total combinations, which is astronomically large even for size1 = 12 and size2 = 12.
The optimal approach uses dynamic programming with bitmasks to represent subsets of the second group that have already been connected. We iterate over each point in the first group and consider connecting it to all possible subsets of the second group, updating a DP table to track the minimum cost for each subset of connections. The key insight is that we can represent the connected state of the second group as a bitmask, allowing us to efficiently store and update states while considering all combinations.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((2^size2 - 1)^size1) | O(1) | Enumerates all valid connection subsets, impractical for max constraints |
| Optimal | O(size1 * 2^size2 * size2) | O(2^size2) | Uses DP with bitmask over subsets of second group, feasible for size <= 12 |
Algorithm Walkthrough
- Initialize a DP array of size
2^size2representing all possible subsets of connected points in the second group. Each elementdp[mask]will store the minimum cost to connect points in the first group processed so far to the subset represented bymask. Initially,dp[0] = 0and all others are infinity. - Iterate over each point
iin the first group. For eachi, initialize a new DP arraynew_dpto store updated costs. We will compute the minimum cost for each subset of connected points after including pointi. - For each subset
maskof the second group, check all pointsjin the second group. For eachj, calculate the new maskmask | (1 << j)representing the union of the current subset and connectingitoj. Updatenew_dp[new_mask]as the minimum of its current value anddp[mask] + cost[i][j]. - After processing all points in the second group for
i, assigndp = new_dp. - After processing all points in the first group, the minimum cost to connect both groups completely is
dp[(1 << size2) - 1], which represents all points in the second group connected.
Why it works: The DP invariant is that dp[mask] always represents the minimum cost to connect the first i points to the subset of the second group represented by mask. By iteratively updating subsets with each point from the first group, we ensure that all connection combinations are considered efficiently without redundant recalculation.
Python Solution
from typing import List
class Solution:
def connectTwoGroups(self, cost: List[List[int]]) -> int:
size1, size2 = len(cost), len(cost[0])
max_mask = 1 << size2
dp = [float('inf')] * max_mask
dp[0] = 0
for i in range(size1):
new_dp = [float('inf')] * max_mask
for mask in range(max_mask):
if dp[mask] == float('inf'):
continue
for j in range(size2):
new_mask = mask | (1 << j)
new_dp[new_mask] = min(new_dp[new_mask], dp[mask] + cost[i][j])
dp = new_dp
return dp[max_mask - 1]
The code initializes a DP array for all possible subsets of the second group. For each point in the first group, it updates the DP array considering connections to each point in the second group. After all points are processed, the last element dp[max_mask - 1] gives the minimum cost for connecting all points in both groups.
Go Solution
import "math"
func connectTwoGroups(cost [][]int) int {
size1, size2 := len(cost), len(cost[0])
maxMask := 1 << size2
dp := make([]int, maxMask)
for i := 1; i < maxMask; i++ {
dp[i] = math.MaxInt32
}
for i := 0; i < size1; i++ {
newDP := make([]int, maxMask)
for k := range newDP {
newDP[k] = math.MaxInt32
}
for mask := 0; mask < maxMask; mask++ {
if dp[mask] == math.MaxInt32 {
continue
}
for j := 0; j < size2; j++ {
newMask := mask | (1 << j)
if dp[mask]+cost[i][j] < newDP[newMask] {
newDP[newMask] = dp[mask] + cost[i][j]
}
}
}
dp = newDP
}
return dp[maxMask-1]
}
The Go implementation follows the same logic as Python but uses math.MaxInt32 to represent infinity. Slices are used for the DP array, and we ensure that each subset update checks for a lower cost before assignment.
Worked Examples
Example 1:
cost = [[15, 96], [36, 2]]
Initial dp = [0, inf, inf, inf]
Processing first point i = 0:
- Connect to
j=0-> new_mask=1, new_dp[1] = 0+15=15 - Connect to
j=1-> new_mask=2, new_dp[2] = 0+96=96
Processing second point i = 1:
- For mask=1, connect to j=0 -> new_mask=1, new_dp[1] = 15+36=51, connect to j=1 -> new_mask=3, new_dp[3]=15+2=17
- For mask=2, connect to j=0 -> new_mask=3, new_dp[3]=min(17, 96+36=132)=17, connect to j=1 -> new_mask=2, new_dp[2]=96+2=98
Final dp = [inf, 51, 98, 17] → answer = 17
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(size1 * 2^size2 * size2) | For each of the size1 points, we iterate over 2^size2 masks and size2 connections |
| Space | O(2^size2) | Only two DP arrays of size 2^size2 are maintained |
The complexity is feasible given the constraints size1, size2 <= 12, making 2^12 = 4096, which is manageable.
Test Cases
# Example tests
assert Solution().connectTwoGroups([[15, 96], [36, 2]]) == 17 # small 2x2 matrix
assert Solution().connectTwoGroups([[1,3,5],[4,1,1],[1,5,3]]) == 4 # 3x3 matrix
assert Solution().connectTwoGroups([[2,5,1],[3,4,7],[8,1,2],[6,2,4],[3,8,8]]) == 10 # 5x3 matrix
# Edge tests
assert Solution().connectTwoGroups([[0]]) == 0 # single point, zero cost
assert Solution().connectTwoGroups([[1]]) == 1 # single point, non-zero cost
assert Solution().connectTwoGroups([[1,