Question
一群朋友去度假,有时互相借钱。
例如,爱丽丝为比尔的午餐支付了10美元。后来克里斯给爱丽丝5美元搭出租车。我们可以假设每笔交易为一个三元组(X,Y,Z),这意味着第 X 个人借给第 Y 个人 Z 美元。假设 Alice,Bill 和 Chris 是第0,1,2 个人(0,1,2是他们的ID),他们之间的交易可以表示为[ [ 0,1,10 ],[ 2,0,5 ] ]。
给定一组人之间的交易清单,返回结算所需的最低交易数量。
example1:
输入:[[0,1,10], [2,0,5]]
输出:2
explanation:
第 0 个人借给第 1 个人10美元
第 2 个人借给第 0 个人 5美元
需要2笔交易,其中一种方式是第1个人还给第0个人和第2个人各5美元。
example2:
输入 [[0,1,10], [1,0,1], [1,2,5], [2,0,5]]
输出 1
explanation:
第0个人借给第1个人10美元
第1个人借给第0个人1美元
第1个人借给第2个人5美元
第2个人借给第0个人5美元
只需要1笔交易,第1个人还给第0个人4美元债务就还清了。
Answer
Solution 1:
DP算法。
- 由题目可知,对应每一个人都有一个借给别人钱的量和一个应还钱给别人的量。如果当下某人的这两个值的和为0,那么其不需要再有其他交易行为了。
- 将所有人的交易情况构成一个收支情况的数组(除去收支平衡,即得数为0的人),如[-3,4,3,-4](这里用正数表示收入,负数表示支出),很显然最优解为2,也就是2—>-2,3—>-3。当然还有另外不是最优的解法,如:3—>-2,1—>-3,2—>-2。从这里我们能够发现,最优解是我们数组的子集([2,-2],[3,-3])的最优解,非最优解([3,-2],[1,-3],[2,-2])内部的[1,-3]并不是数组的子集。
- 那么由此,我们可以想到,枚举数组中所有的子集组合,找到每个子集的最优解从而得到问题的最优解。
- 我们将一个集合的子集定义为只有0,1的向量,如:0110,这里表示该子集中由第二和第三个元素组成。如我们上面的数组[-3,4,3,-4],则其子集有:0000,0001,…,1111,共16=2^4种子集情况。
- 同时以
1<<j & i
来表示在第i个子集中包含有第j个元素。当sum=0
时,表示收支平衡,计算该子问题的最优解。
class Solution {
public int minTransfers(int[][] transactions){
Map<Integer, Integer> debt = new HashMap<>();
// 预处理收支情况
for (int[] t : transactions){
debt.put(t[0], debt.getOrDefault(t[0], 0) - t[2]);
debt.put(t[1], debt.getOrDefault(t[1], 0) + t[2]);
}
//除去收支平衡的人
int[] account = new int[debt.size()];
int len = 0;
for (int v : debt.values()){
if ( v != 0){
account[len++] = v;
}
}
if (len == 0) return 0;
// 1 << len == 1 * 2^len
int[] dp = new int[1 << len];
Arrays.fill(dp, Integer.MAX_VALUE/2);
for (int i = 1; i < dp.length; i++){ //枚举每一个子集
int sum = 0, count = 0;
for (int j = 0; j < len; j++){
if ((1 << j & i) != 0){ //这个子集里有第j个人
sum += account[j]; // 加上第j个人的收支情况
count++; //总共的相加次数
}
}
if (sum == 0){ //如果收支平衡,那么该子集是一个子问题
dp[i] = count -1; //这个子集需要的最大交易数
for (int j = i; j < i; j++){ //枚举这个子问题的子集
if (((i & j) == j) && dp[j] + dp[i-j] < dp[i]){
dp[i] = dp[j] + dp[i-j]; // 求子问题的最优解
}
}
}
}
return dp[dp.length - 1]; // 返回总问题的最优解
}
}
- 时间复杂度:O(4^n),其中,枚举子集的时间复杂度为O(2^n),对子集进行最优解分析枚举子集时间复杂度也为O(2^n),则O(2^n * 2^n) = O(4^n).
- 空间复杂度:O(2^n),即为dp占用的大小。