给你两组点,其中第一组中有 size1
个点,第二组中有 size2
个点,且 size1 >= size2
。
任意两点间的连接成本 cost
由大小为 size1 x size2
矩阵给出,其中 cost[i][j]
是第一组中的点 i
和第二组中的点 j
的连接成本。 如果两个组中的每个点都与另一组中的一个或多个点连接,则称这两组点是连通的。 换言之,第一组中的每个点必须至少与第二组中的一个点连接,且第二组中的每个点必须至少与第一组中的一个点连接。
返回连通两组点所需的最小成本。
示例 1:
![](https://assets.leetcode-cn.com/aliyun-lc- upload/uploads/2020/09/20/ex1.jpg)
**输入:** cost = [[15, 96], [36, 2]]
**输出:** 17
**解释:** 连通两组点的最佳方法是:
1--A
2--B
总成本为 17 。
示例 2:
![](https://assets.leetcode-cn.com/aliyun-lc- upload/uploads/2020/09/20/ex2.jpg)
**输入:** cost = [[1, 3, 5], [4, 1, 1], [1, 5, 3]]
**输出:** 4
**解释:** 连通两组点的最佳方法是:
1--A
2--B
2--C
3--A
最小成本为 4 。
请注意,虽然有多个点连接到第一组中的点 2 和第二组中的点 A ,但由于题目并不限制连接点的数目,所以只需要关心最低总成本。
示例 3:
**输入:** cost = [[2, 5, 1], [3, 4, 7], [8, 1, 2], [6, 2, 4], [3, 8, 8]]
**输出:** 10
提示:
size1 == cost.length
size2 == cost[i].length
1 <= size1, size2 <= 12
size1 >= size2
0 <= cost[i][j] <= 100
方法一:状态压缩 + 动态规划 记第一组点数为 size}_1,第二组点数为 size}_2。根据数据范围,我们可以使用二进制数 s 来表示一个点集,s 的第 k 位为 1 表示第 k 个点在点集 s 中,s 的第 k 位为 0 表示第 k 个点不在点集 s 中。使用 dp}[i][s] 表示第一组的前 i 个点(前 i 个点指第 0, 1, 2, …, i - 1 个点)与第二组的点集 s 的最小连通成本(因为 size}_1 \ge \textit{size}_2,所以将第二组作为点集),有四种情况:
i = 0 且 s = 0:
两组点都为空,因此最小连通成本为 dp}[0][0] = 0。
i = 0 且 s \ne 0:
第一组的点为空,第二组的点不为空,因此无法连通,令 dp}[0][s] = \infty。
i \ne 0 且 s = 0:
第一组的点不为空,第二组的点为空,因此无法连通,令 dp}[i][0] = \infty。
i \ne 0 且 s \ne 0:
考虑第一组第 i - 1 个点与第二组点集 s 的第 k 个点连接,使用 s_{-k 表示点集 s 去除第 k 个点后的剩余点集,那么连通成本 c 有三种情况:
第二组点集 s 的第 k 个点不再与其他点连接,那么 c = \textit{dp}[i][s_{-k}] + \textit{cost}[i - 1][k];
第一组第 i - 1 个点不再与其他点连接,那么 c = \textit{dp}[i - 1][s] + \textit{cost}[i - 1][k];
第一组第 i - 1 个点和第二组点集 s 的第 k 个点都不再与其他点连接,那么 c = \textit{dp}[i - 1][s_{-k}] + \textit{cost}[i - 1][k]。
枚举第一组第 i - 1 个点与第二组点集 s 中任一 k \in s 的点连接,那么状态转移方程如下:
\textit{dp}[i][s] = \min_{k \in s} \big { {\min { { \textit{dp}[i][s_{-k}], \textit{dp}[i - 1][s], \textit{dp}[i - 1][s_{-k}] } } + \textit{cost}[i - 1][k]} \big }
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution {public : int connectTwoGroups (vector<vector<int >>& cost) { int size1 = cost.size (), size2 = cost[0 ].size (), m = 1 << size2; vector<vector<int >> dp (size1 + 1 , vector <int >(m, INT_MAX / 2 )); dp[0 ][0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp[i][s] = min (dp[i][s], dp[i][s ^ (1 << k)] + cost[i - 1 ][k]); dp[i][s] = min (dp[i][s], dp[i - 1 ][s] + cost[i - 1 ][k]); dp[i][s] = min (dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost[i - 1 ][k]); } } } return dp[size1][m - 1 ]; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int connectTwoGroups (List<List<Integer>> cost) { int size1 = cost.size(), size2 = cost.get(0 ).size(), m = 1 << size2; int [][] dp = new int [size1 + 1 ][m]; for (int i = 0 ; i <= size1; i++) { Arrays.fill(dp[i], Integer.MAX_VALUE / 2 ); } dp[0 ][0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp[i][s] = Math.min(dp[i][s], dp[i][s ^ (1 << k)] + cost.get(i - 1 ).get(k)); dp[i][s] = Math.min(dp[i][s], dp[i - 1 ][s] + cost.get(i - 1 ).get(k)); dp[i][s] = Math.min(dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost.get(i - 1 ).get(k)); } } } return dp[size1][m - 1 ]; } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Solution { public int ConnectTwoGroups (IList<IList<int >> cost ) { int size1 = cost.Count, size2 = cost[0 ].Count, m = 1 << size2; int [][] dp = new int [size1 + 1 ][]; for (int i = 0 ; i <= size1; i++) { dp[i] = new int [m]; Array.Fill(dp[i], int .MaxValue / 2 ); } dp[0 ][0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp[i][s] = Math.Min(dp[i][s], dp[i][s ^ (1 << k)] + cost[i - 1 ][k]); dp[i][s] = Math.Min(dp[i][s], dp[i - 1 ][s] + cost[i - 1 ][k]); dp[i][s] = Math.Min(dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost[i - 1 ][k]); } } } return dp[size1][m - 1 ]; } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func min (a, b int ) int { if a > b { return b } return a } func connectTwoGroups (cost [][]int ) int { size1, size2, m := len (cost), len (cost[0 ]), 1 << len (cost[0 ]) dp := make ([][]int , size1 + 1 ) for i := 0 ; i <= size1; i++ { dp[i] = make ([]int , m) } for s := 1 ; s < m; s++ { dp[0 ][s] = 0x3f3f3f3f } for i := 1 ; i <= size1; i++ { for s := 0 ; s < m; s++ { dp[i][s] = 0x3f3f3f3f for k := 0 ; k < size2; k++ { if (s & (1 << k)) == 0 { continue } dp[i][s] = min(dp[i][s], dp[i][s ^ (1 << k)] + cost[i - 1 ][k]) dp[i][s] = min(dp[i][s], dp[i - 1 ][s] + cost[i - 1 ][k]) dp[i][s] = min(dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost[i - 1 ][k]) } } } return dp[size1][m - 1 ] }
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int connectTwoGroups (int ** cost, int costSize, int * costColSize) { int size1 = costSize, size2 = costColSize[0 ], m = 1 << size2; int dp[size1 + 1 ][m]; memset (dp, 0x3f , sizeof (dp)); dp[0 ][0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp[i][s] = fmin(dp[i][s], dp[i][s ^ (1 << k)] + cost[i - 1 ][k]); dp[i][s] = fmin(dp[i][s], dp[i - 1 ][s] + cost[i - 1 ][k]); dp[i][s] = fmin(dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost[i - 1 ][k]); } } } return dp[size1][m - 1 ]; }
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution : def connectTwoGroups (self, cost: List [List [int ]] ) -> int : size1, size2 = len (cost), len (cost[0 ]) m = 1 << size2 dp = [[float ("inf" )] * m for _ in range (size1 + 1 )] dp[0 ][0 ] = 0 for i in range (1 , size1 + 1 ): for s in range (m): for k in range (size2): if (s & (1 << k)) == 0 : continue dp[i][s] = min (dp[i][s], dp[i][s ^ (1 << k)] + cost[i - 1 ][k]) dp[i][s] = min (dp[i][s], dp[i - 1 ][s] + cost[i - 1 ][k]) dp[i][s] = min (dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost[i - 1 ][k]) return dp[size1][m - 1 ]
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var connectTwoGroups = function (cost ) { const size1 = cost.length ; const size2 = cost[0 ].length ; const m = 1 << size2; const dp = Array .from (Array (size1 + 1 ), () => new Array (m).fill (Number .MAX_SAFE_INTEGER / 2 )); dp[0 ][0 ] = 0 ; for (let i = 1 ; i <= size1; i++) { for (let s = 0 ; s < m; s++) { for (let k = 0 ; k < size2; k++) { if ((s & (1 << k)) === 0 ) { continue ; } dp[i][s] = Math .min (dp[i][s], dp[i][s ^ (1 << k)] + cost[i - 1 ][k]); dp[i][s] = Math .min (dp[i][s], dp[i - 1 ][s] + cost[i - 1 ][k]); dp[i][s] = Math .min (dp[i][s], dp[i - 1 ][s ^ (1 << k)] + cost[i - 1 ][k]); } } } return dp[size1][m - 1 ]; };
转移方程的 dp[i][] 计算只与 dp[i - 1][ ] 和 dp[i][*] 相关,因此我们可以只使用一维数组来保存,从而节省空间。
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution {public : int connectTwoGroups (vector<vector<int >>& cost) { int size1 = cost.size (), size2 = cost[0 ].size (), m = 1 << size2; vector<int > dp1 (m, INT_MAX / 2 ) , dp2 (m) ; dp1[0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { dp2[s] = INT_MAX / 2 ; for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp2[s] = min (dp2[s], dp2[s ^ (1 << k)] + cost[i - 1 ][k]); dp2[s] = min (dp2[s], dp1[s] + cost[i - 1 ][k]); dp2[s] = min (dp2[s], dp1[s ^ (1 << k)] + cost[i - 1 ][k]); } } dp1.swap (dp2); } return dp1[m - 1 ]; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int connectTwoGroups (List<List<Integer>> cost) { int size1 = cost.size(), size2 = cost.get(0 ).size(), m = 1 << size2; int [] dp1 = new int [m]; Arrays.fill(dp1, Integer.MAX_VALUE / 2 ); int [] dp2 = new int [m]; dp1[0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { dp2[s] = Integer.MAX_VALUE / 2 ; for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp2[s] = Math.min(dp2[s], dp2[s ^ (1 << k)] + cost.get(i - 1 ).get(k)); dp2[s] = Math.min(dp2[s], dp1[s] + cost.get(i - 1 ).get(k)); dp2[s] = Math.min(dp2[s], dp1[s ^ (1 << k)] + cost.get(i - 1 ).get(k)); } } System.arraycopy(dp2, 0 , dp1, 0 , m); } return dp1[m - 1 ]; } }
[sol2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Solution { public int ConnectTwoGroups (IList<IList<int >> cost ) { int size1 = cost.Count, size2 = cost[0 ].Count, m = 1 << size2; int [] dp1 = new int [m]; Array.Fill(dp1, int .MaxValue / 2 ); int [] dp2 = new int [m]; dp1[0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { dp2[s] = int .MaxValue / 2 ; for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp2[s] = Math.Min(dp2[s], dp2[s ^ (1 << k)] + cost[i - 1 ][k]); dp2[s] = Math.Min(dp2[s], dp1[s] + cost[i - 1 ][k]); dp2[s] = Math.Min(dp2[s], dp1[s ^ (1 << k)] + cost[i - 1 ][k]); } } Array.Copy(dp2, 0 , dp1, 0 , m); } return dp1[m - 1 ]; } }
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func min (a, b int ) int { if a > b { return b } return a } func connectTwoGroups (cost [][]int ) int { size1, size2, m := len (cost), len (cost[0 ]), 1 << len (cost[0 ]) dp1, dp2 := make ([]int , m), make ([]int , m) for s := 1 ; s < m; s++ { dp1[s] = 0x3f3f3f3f } for i := 1 ; i <= size1; i++ { for s := 0 ; s < m; s++ { dp2[s] = 0x3f3f3f3f for k := 0 ; k < size2; k++ { if (s & (1 << k)) == 0 { continue } dp2[s] = min(dp2[s], dp2[s ^ (1 << k)] + cost[i - 1 ][k]) dp2[s] = min(dp2[s], dp1[s] + cost[i - 1 ][k]) dp2[s] = min(dp2[s], dp1[s ^ (1 << k)] + cost[i - 1 ][k]) } } dp1, dp2 = dp2, dp1 } return dp1[m - 1 ] }
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int connectTwoGroups (int ** cost, int costSize, int * costColSize) { int size1 = costSize, size2 = costColSize[0 ], m = 1 << size2; int dp1[m], dp2[m]; memset (dp1, 0x3f , sizeof (dp1)); dp1[0 ] = 0 ; for (int i = 1 ; i <= size1; i++) { for (int s = 0 ; s < m; s++) { dp2[s] = INT_MAX / 2 ; for (int k = 0 ; k < size2; k++) { if ((s & (1 << k)) == 0 ) { continue ; } dp2[s] = fmin(dp2[s], dp2[s ^ (1 << k)] + cost[i - 1 ][k]); dp2[s] = fmin(dp2[s], dp1[s] + cost[i - 1 ][k]); dp2[s] = fmin(dp2[s], dp1[s ^ (1 << k)] + cost[i - 1 ][k]); } } memcpy (dp1, dp2, sizeof (dp2)); } return dp1[m - 1 ]; }
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution : def connectTwoGroups (self, cost: List [List [int ]] ) -> int : size1, size2 = len (cost), len (cost[0 ]) m = 1 << size2 dp1, dp2 = [float ("inf" )] * m, [0 ] * m dp1[0 ] = 0 for i in range (1 , size1 + 1 ): for s in range (m): dp2[s] = float ("inf" ) for k in range (size2): if (s & (1 << k)) == 0 : continue dp2[s] = min (dp2[s], dp2[s ^ (1 << k)] + cost[i - 1 ][k]) dp2[s] = min (dp2[s], dp1[s] + cost[i - 1 ][k]) dp2[s] = min (dp2[s], dp1[s ^ (1 << k)] + cost[i - 1 ][k]) dp1 = dp2[:] return dp1[m - 1 ]
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 var connectTwoGroups = function (cost ) { const size1 = cost.length ; const size2 = cost[0 ].length ; const m = 1 << size2; const dp1 = new Array (m).fill (Number .MAX_SAFE_INTEGER / 2 ); const dp2 = new Array (m); dp1[0 ] = 0 ; for (let i = 1 ; i <= size1; i++) { for (let s = 0 ; s < m; s++) { dp2[s] = Number .MAX_SAFE_INTEGER / 2 ; for (let k = 0 ; k < size2; k++) { if ((s & (1 << k)) === 0 ) { continue ; } dp2[s] = Math .min (dp2[s], dp2[s ^ (1 << k)] + cost[i - 1 ][k]); dp2[s] = Math .min (dp2[s], dp1[s] + cost[i - 1 ][k]); dp2[s] = Math .min (dp2[s], dp1[s ^ (1 << k)] + cost[i - 1 ][k]); } } dp1.splice (0 , m, ...dp2); } return dp1[m - 1 ]; };
复杂度分析