Facebook Pixel

1595. Minimum Cost to Connect Two Groups of Points

Problem Description

You have two groups of points. The first group contains size₁ points and the second group contains size₂ points, where size₁ ≥ size₂.

You're given a cost matrix of dimensions size₁ × size₂ where cost[i][j] represents the cost to connect point i from the first group to point j from the second group.

Your task is to connect the two groups with the following requirements:

  • Every point in the first group must be connected to at least one point in the second group
  • Every point in the second group must be connected to at least one point in the first group

A single point can be connected to multiple points in the opposite group. The goal is to find the minimum total cost to satisfy these connection requirements.

For example, if you have:

  • First group: 3 points (A, B, C)
  • Second group: 2 points (X, Y)
  • Cost matrix showing connection costs between each pair

You need to ensure all points A, B, C are connected to at least one of {X, Y}, and both X and Y are connected to at least one of {A, B, C}. The connections you choose should minimize the total cost.

The solution uses dynamic programming with bitmask to track which points in the second group have been connected, iterating through points in the first group and considering all possible connection combinations.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing this as an assignment problem with special constraints. Since every point must be connected to at least one point in the opposite group, we need to track which connections have been made.

Let's think about how to build the solution incrementally. As we process points from the first group one by one, we need to know which points in the second group have already been connected. This is where the bitmask comes in - we can use a binary number where each bit represents whether a point in the second group has been connected or not.

For example, if the second group has 3 points and our bitmask is 101, it means points 0 and 2 are connected, but point 1 is not yet connected.

The dynamic programming state f[i][j] represents: "What's the minimum cost to connect the first i points from group 1, such that the points in group 2 that are connected form the pattern j (as a bitmask)?"

When we're at point i in the first group, we have choices:

  1. We can connect it to a point k in the second group that wasn't connected before (flipping that bit in our mask)
  2. We can connect it to a point k that was already connected (the mask doesn't change, but we still pay the connection cost)

The beauty of this approach is that by the time we reach f[m][2^n - 1] (where m is the size of group 1 and 2^n - 1 means all bits are set for group 2), we've ensured:

  • All points in group 1 have been processed and connected
  • All points in group 2 are connected (all bits in the mask are 1)

This guarantees both constraints are satisfied while tracking the minimum cost along the way.

Learn more about Dynamic Programming and Bitmask patterns.

Solution Approach

We implement a dynamic programming solution with bitmask to track connections to the second group.

State Definition:

  • f[i][j] = minimum cost to connect the first i points from group 1, where j is a bitmask representing which points in group 2 are connected
  • m = size of first group, n = size of second group

Initialization:

f = [[inf] * (1 << n) for _ in range(m + 1)]
f[0][0] = 0
  • Create a 2D DP table with dimensions (m+1) × 2^n
  • f[0][0] = 0 means no points connected, zero cost
  • All other states start with infinity

State Transitions: For each point i in the first group (1 to m):

for i in range(1, m + 1):
    for j in range(1 << n):  # iterate through all possible bitmasks
        for k in range(n):    # try connecting to each point in group 2

For each valid connection to point k in group 2 (when bit k is set in mask j):

if (j >> k & 1) == 0:
    continue  # skip if bit k is not set in mask j

Calculate minimum cost from three possible previous states:

c = cost[i - 1][k]  # cost to connect point i-1 to point k
x = min(
    f[i][j ^ (1 << k)],      # Case 1: point k wasn't connected before
    f[i - 1][j],              # Case 2: point k was already connected, keep it
    f[i - 1][j ^ (1 << k)]    # Case 3: point k wasn't connected, now connecting
) + c
f[i][j] = min(f[i][j], x)

The three cases represent:

  1. Same row, flip bit k: We've already processed point i, but point k in group 2 wasn't connected
  2. Previous row, same mask: Point k was already connected by previous points
  3. Previous row, flip bit k: Point k gets its first connection from point i

Final Answer:

return f[m][-1]  # f[m][2^n - 1]

Returns the minimum cost when all m points from group 1 are processed and all n points from group 2 are connected (all bits set in the mask).

The algorithm ensures both constraints are met: every point in group 1 is connected (we process all of them), and every point in group 2 is connected (final mask has all bits set).

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example to illustrate the solution approach.

Example Setup:

  • First group: 3 points (indexed 0, 1, 2)
  • Second group: 2 points (indexed 0, 1)
  • Cost matrix:
cost = [[1, 4],
        [3, 2],
        [5, 1]]

This means:

  • Connecting point 0 (group 1) to point 0 (group 2) costs 1
  • Connecting point 0 (group 1) to point 1 (group 2) costs 4
  • And so on...

DP Table Initialization: We create f[4][4] since we have 3 points in group 1 (need indices 0-3) and 2^2=4 possible bitmasks for group 2.

  • Bitmask 00 (decimal 0): no points in group 2 connected
  • Bitmask 01 (decimal 1): only point 0 in group 2 connected
  • Bitmask 10 (decimal 2): only point 1 in group 2 connected
  • Bitmask 11 (decimal 3): both points in group 2 connected

Initial state: f[0][0] = 0, all others = infinity

Processing Point 0 from Group 1:

When i=1 (processing first point), we consider connecting it to points in group 2:

For mask j=1 (binary 01 - point 0 in group 2 is connected):

  • We can connect point 0 (group 1) to point 0 (group 2), cost = 1
  • Previous state: f[0][0] = 0 (no connections before)
  • New state: f[1][1] = 0 + 1 = 1

For mask j=2 (binary 10 - point 1 in group 2 is connected):

  • We can connect point 0 (group 1) to point 1 (group 2), cost = 4
  • Previous state: f[0][0] = 0
  • New state: f[1][2] = 0 + 4 = 4

For mask j=3 (binary 11 - both points in group 2 connected):

  • Option 1: Connect to point 0 (cost 1), coming from f[0][2] or f[1][2]
  • Option 2: Connect to point 1 (cost 4), coming from f[0][1] or f[1][1]
  • Best: f[1][3] = min(f[1][2] + 1, f[1][1] + 4) = min(4+1, 1+4) = 5

Processing Point 1 from Group 1:

When i=2, key transitions:

For mask j=3 (both points connected):

  • Connect point 1 to point 0 (cost 3):
    • From f[1][3] = 5 (point 0 already connected): total = 5 + 3 = 8
    • From f[1][2] = 4 (connecting point 0 for first time): total = 4 + 3 = 7
  • Connect point 1 to point 1 (cost 2):
    • From f[1][3] = 5 (point 1 already connected): total = 5 + 2 = 7
    • From f[1][1] = 1 (connecting point 1 for first time): total = 1 + 2 = 3
  • Best: f[2][3] = 3

Processing Point 2 from Group 1:

When i=3, for the final mask j=3:

  • Connect point 2 to point 0 (cost 5): best path gives 3 + 5 = 8
  • Connect point 2 to point 1 (cost 1): best path gives 3 + 1 = 4
  • Best: f[3][3] = 4

Final Answer: f[3][3] = 4

This corresponds to the optimal solution:

  • Point 0 (group 1) connects to point 0 (group 2): cost 1
  • Point 1 (group 1) connects to point 1 (group 2): cost 2
  • Point 2 (group 1) connects to point 1 (group 2): cost 1
  • Total cost: 1 + 2 + 1 = 4

All points in both groups are connected, satisfying our constraints with minimum total cost.

Solution Implementation

1class Solution:
2    def connectTwoGroups(self, cost: List[List[int]]) -> int:
3        # Get dimensions of the cost matrix
4        group1_size, group2_size = len(cost), len(cost[0])
5      
6        # Initialize DP table
7        # dp[i][mask] = minimum cost to connect first i points from group1
8        # with points from group2 represented by mask
9        dp = [[float('inf')] * (1 << group2_size) for _ in range(group1_size + 1)]
10      
11        # Base case: no points from group1 connected to empty set costs 0
12        dp[0][0] = 0
13      
14        # Iterate through each point in group1
15        for i in range(1, group1_size + 1):
16            # Iterate through all possible subsets of group2 (represented as bitmask)
17            for mask in range(1 << group2_size):
18                # Try connecting current point from group1 to each point in group2
19                for j in range(group2_size):
20                    # Check if j-th point of group2 is in the current mask
21                    if (mask >> j & 1) == 0:
22                        continue
23                  
24                    # Cost of connecting (i-1)th point of group1 to j-th point of group2
25                    connection_cost = cost[i - 1][j]
26                  
27                    # Calculate minimum cost from three possible transitions:
28                    # 1. Current point reuses existing connection (dp[i][mask ^ (1 << j)])
29                    # 2. Previous points already covered this mask (dp[i - 1][mask])
30                    # 3. Add new connection from previous state (dp[i - 1][mask ^ (1 << j)])
31                    min_previous_cost = min(
32                        dp[i][mask ^ (1 << j)],  # Remove j from mask for current row
33                        dp[i - 1][mask],          # All connections already made by previous points
34                        dp[i - 1][mask ^ (1 << j)]  # Add connection from previous row
35                    )
36                  
37                    # Update minimum cost for current state
38                    dp[i][mask] = min(dp[i][mask], min_previous_cost + connection_cost)
39      
40        # Return minimum cost when all points from both groups are connected
41        # (all bits set in the mask)
42        return dp[group1_size][(1 << group2_size) - 1]
43
1class Solution {
2    public int connectTwoGroups(List<List<Integer>> cost) {
3        int leftGroupSize = cost.size();
4        int rightGroupSize = cost.get(0).size();
5        final int INFINITY = 1 << 30;
6      
7        // dp[i][mask] represents the minimum cost to connect first i nodes from left group
8        // with nodes from right group represented by the bitmask
9        int[][] dp = new int[leftGroupSize + 1][1 << rightGroupSize];
10      
11        // Initialize all states with infinity
12        for (int[] row : dp) {
13            Arrays.fill(row, INFINITY);
14        }
15      
16        // Base case: no nodes connected, cost is 0
17        dp[0][0] = 0;
18      
19        // Process each node from the left group
20        for (int leftNode = 1; leftNode <= leftGroupSize; leftNode++) {
21            // Try all possible bitmask states for right group connections
22            for (int rightMask = 0; rightMask < (1 << rightGroupSize); rightMask++) {
23                // Check each node in the right group
24                for (int rightNode = 0; rightNode < rightGroupSize; rightNode++) {
25                    // If this right node is included in the current mask
26                    if ((rightMask >> rightNode & 1) == 1) {
27                        int connectionCost = cost.get(leftNode - 1).get(rightNode);
28                      
29                        // Option 1: Connect current left node to this right node 
30                        // (right node wasn't connected before)
31                        dp[leftNode][rightMask] = Math.min(
32                            dp[leftNode][rightMask], 
33                            dp[leftNode][rightMask ^ (1 << rightNode)] + connectionCost
34                        );
35                      
36                        // Option 2: Connect current left node to already connected right node
37                        // (right node was already connected to previous left nodes)
38                        dp[leftNode][rightMask] = Math.min(
39                            dp[leftNode][rightMask], 
40                            dp[leftNode - 1][rightMask] + connectionCost
41                        );
42                      
43                        // Option 3: Connect current left node to this right node
44                        // (this is the first connection for this right node)
45                        dp[leftNode][rightMask] = Math.min(
46                            dp[leftNode][rightMask], 
47                            dp[leftNode - 1][rightMask ^ (1 << rightNode)] + connectionCost
48                        );
49                    }
50                }
51            }
52        }
53      
54        // Return minimum cost when all left nodes are processed 
55        // and all right nodes are connected (all bits set)
56        return dp[leftGroupSize][(1 << rightGroupSize) - 1];
57    }
58}
59
1class Solution {
2public:
3    int connectTwoGroups(vector<vector<int>>& cost) {
4        int leftGroupSize = cost.size();
5        int rightGroupSize = cost[0].size();
6      
7        // dp[i][mask] represents the minimum cost to connect first i nodes from left group
8        // with nodes from right group represented by mask (bit set means connected)
9        int dp[leftGroupSize + 1][1 << rightGroupSize];
10      
11        // Initialize with large values (0x3f3f3f3f after memset)
12        memset(dp, 0x3f, sizeof(dp));
13      
14        // Base case: no nodes from left group, no connections needed
15        dp[0][0] = 0;
16      
17        // Process each node from left group
18        for (int leftNode = 1; leftNode <= leftGroupSize; ++leftNode) {
19            // Try all possible connection states for right group
20            for (int rightMask = 0; rightMask < (1 << rightGroupSize); ++rightMask) {
21                // Try connecting current left node to each right node in the mask
22                for (int rightNode = 0; rightNode < rightGroupSize; ++rightNode) {
23                    // Check if this right node is in the current mask
24                    if ((rightMask >> rightNode) & 1) {
25                        // Cost of connecting current left node to this right node
26                        int connectionCost = cost[leftNode - 1][rightNode];
27                      
28                        // Calculate minimum cost from three possible transitions:
29                        // 1. Connect current left node to an already connected right node
30                        // 2. Previous left nodes already connected to all nodes in mask
31                        // 3. Previous left nodes connected to mask excluding current right node
32                        int previousMask = rightMask ^ (1 << rightNode);
33                        int minPreviousCost = min({
34                            dp[leftNode][previousMask],      // Case 1
35                            dp[leftNode - 1][rightMask],      // Case 2
36                            dp[leftNode - 1][previousMask]    // Case 3
37                        });
38                      
39                        // Update dp with minimum cost
40                        dp[leftNode][rightMask] = min(dp[leftNode][rightMask], 
41                                                      minPreviousCost + connectionCost);
42                    }
43                }
44            }
45        }
46      
47        // Return minimum cost when all left nodes are processed
48        // and all right nodes are connected (all bits set in mask)
49        return dp[leftGroupSize][(1 << rightGroupSize) - 1];
50    }
51};
52
1/**
2 * Finds the minimum cost to connect two groups where every element in both groups must be connected
3 * Uses dynamic programming with bitmask to track which elements in the second group are connected
4 * 
5 * @param cost - 2D array where cost[i][j] represents the cost of connecting element i from first group to element j from second group
6 * @returns The minimum total cost to connect all elements
7 */
8function connectTwoGroups(cost: number[][]): number {
9    const firstGroupSize: number = cost.length;
10    const secondGroupSize: number = cost[0].length;
11    const INFINITY: number = 1 << 30;
12  
13    // dp[i][mask] represents the minimum cost to connect first i elements of group 1
14    // with elements of group 2 represented by the bitmask
15    const dp: number[][] = Array(firstGroupSize + 1)
16        .fill(0)
17        .map(() => Array(1 << secondGroupSize).fill(INFINITY));
18  
19    // Base case: no elements connected, cost is 0
20    dp[0][0] = 0;
21  
22    // Iterate through each element in the first group
23    for (let i = 1; i <= firstGroupSize; ++i) {
24        // Iterate through all possible bitmask states for the second group
25        for (let mask = 0; mask < (1 << secondGroupSize); ++mask) {
26            // Try connecting element i-1 from first group to each element in second group
27            for (let j = 0; j < secondGroupSize; ++j) {
28                // Check if j-th element of second group is included in current mask
29                if (((mask >> j) & 1) === 1) {
30                    const connectionCost: number = cost[i - 1][j];
31                  
32                    // Option 1: Connect i-th element to j-th element, j was not connected before
33                    dp[i][mask] = Math.min(dp[i][mask], dp[i][mask ^ (1 << j)] + connectionCost);
34                  
35                    // Option 2: Connect i-th element to j-th element, j was already connected
36                    dp[i][mask] = Math.min(dp[i][mask], dp[i - 1][mask] + connectionCost);
37                  
38                    // Option 3: Connect both i-th element and establish j-th connection
39                    dp[i][mask] = Math.min(dp[i][mask], dp[i - 1][mask ^ (1 << j)] + connectionCost);
40                }
41            }
42        }
43    }
44  
45    // Return minimum cost when all elements from first group are processed
46    // and all elements from second group are connected (all bits set to 1)
47    return dp[firstGroupSize][(1 << secondGroupSize) - 1];
48}
49

Time and Space Complexity

Time Complexity: O(m * 2^n * n)

The algorithm uses dynamic programming with bitmask to track which points in the second group have been connected. There are three nested loops:

  • The outer loop iterates through m points in the first group: O(m)
  • The middle loop iterates through all possible bitmask states for the second group: O(2^n)
  • The inner loop iterates through each point in the second group: O(n)

For each combination of (i, j, k), we perform constant time operations to calculate the minimum cost. Therefore, the overall time complexity is O(m * 2^n * n).

Space Complexity: O(m * 2^n)

The algorithm uses a 2D DP table f with dimensions (m + 1) × 2^n. Each cell stores a single integer value representing the minimum cost for a specific state. The space required is:

  • DP table f: (m + 1) * 2^n entries
  • Other variables use constant space

Therefore, the overall space complexity is O(m * 2^n).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Base Case Initialization

A common mistake is initializing dp[0][mask] = 0 for all masks, not just mask 0. This would incorrectly suggest that points from group 2 can be connected with zero cost when no points from group 1 have been processed.

Wrong:

dp = [[0] * (1 << group2_size) for _ in range(group1_size + 1)]

Correct:

dp = [[float('inf')] * (1 << group2_size) for _ in range(group1_size + 1)]
dp[0][0] = 0

2. Missing State Transitions

Developers often forget one of the three critical transitions, particularly the case where the current point in group 1 connects to an already-connected point in group 2. This leads to suboptimal solutions.

Incomplete transition (missing reuse case):

min_previous_cost = min(
    dp[i - 1][mask],
    dp[i - 1][mask ^ (1 << j)]
)

Complete transition:

min_previous_cost = min(
    dp[i][mask ^ (1 << j)],      # Current row, different mask
    dp[i - 1][mask],              # Previous row, same mask
    dp[i - 1][mask ^ (1 << j)]    # Previous row, different mask
)

3. Incorrect Bit Manipulation

Using wrong bit operations or checking bit positions incorrectly is a frequent error.

Wrong bit check:

if mask & (1 << j) == 0:  # This checks if bit j is NOT set
    continue

Correct for this algorithm:

if (mask >> j & 1) == 0:  # Skip if bit j is not set
    continue

4. Off-by-One Errors in Indexing

Since the DP table uses 1-based indexing for group 1 points but the cost matrix uses 0-based indexing, it's easy to mix them up.

Wrong:

connection_cost = cost[i][j]  # Using i directly

Correct:

connection_cost = cost[i - 1][j]  # Adjust for 0-based cost matrix

5. Incorrect Final Answer Extraction

Some implementations might check all possible masks instead of just the full mask where all group 2 points are connected.

Wrong:

return min(dp[group1_size])  # This doesn't ensure all group2 points are connected

Correct:

return dp[group1_size][(1 << group2_size) - 1]  # Ensures all bits are set

6. Memory Optimization Confusion

When trying to optimize space by using only two rows, developers might incorrectly handle the transition dp[i][mask ^ (1 << j)] which requires the current row.

Solution: If implementing space optimization, process masks in a specific order or use a temporary array to avoid overwriting needed values:

# Process masks in decreasing order when space-optimizing
for mask in range((1 << group2_size) - 1, -1, -1):
    # transitions here
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using input 56?

1KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13    def dfs(path, res):
14        if len(path) == len(digits):
15            res.append(''.join(path))
16            return
17
18        next_number = digits[len(path)]
19        for letter in KEYBOARD[next_number]:
20            path.append(letter)
21            dfs(path, res)
22            path.pop()
23
24    res = []
25    dfs([], res)
26    return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2    '2', "abc".toCharArray(),
3    '3', "def".toCharArray(),
4    '4', "ghi".toCharArray(),
5    '5', "jkl".toCharArray(),
6    '6', "mno".toCharArray(),
7    '7', "pqrs".toCharArray(),
8    '8', "tuv".toCharArray(),
9    '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13    List<String> res = new ArrayList<>();
14    dfs(new StringBuilder(), res, digits.toCharArray());
15    return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19    if (path.length() == digits.length) {
20        res.add(path.toString());
21        return;
22    }
23    char next_digit = digits[path.length()];
24    for (char letter : KEYBOARD.get(next_digit)) {
25        path.append(letter);
26        dfs(path, res, digits);
27        path.deleteCharAt(path.length() - 1);
28    }
29}
30
1const KEYBOARD = {
2    '2': 'abc',
3    '3': 'def',
4    '4': 'ghi',
5    '5': 'jkl',
6    '6': 'mno',
7    '7': 'pqrs',
8    '8': 'tuv',
9    '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13    let res = [];
14    dfs(digits, [], res);
15    return res;
16}
17
18function dfs(digits, path, res) {
19    if (path.length === digits.length) {
20        res.push(path.join(''));
21        return;
22    }
23    let next_number = digits.charAt(path.length);
24    for (let letter of KEYBOARD[next_number]) {
25        path.push(letter);
26        dfs(digits, path, res);
27        path.pop();
28    }
29}
30

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More