1、题目描述
2、解题思路
本题要从繁杂的描述中总结核心问题。
本题其实是问整体的平债(所有人的钱都为 0)需要最少需要多少次。
假设现在有三个人ABC,分别拥有 -5、+3、+2 的钱,要让三个人的钱都为 0 ,至少需要两步,即 B 给 A +3 元,C 给 A +3 元,于是平账结束。
因为所有人一开始都是谁都不借谁,谁都不欠谁的状态,因此,平账后的结果也应该是谁都不借谁,谁都不欠谁的状态。
解体思路就是先给每一个人设置一个账号,初始都是 0 ,在复杂的借钱还钱之后,对每个人剩余的钱来处理:
1、谁的剩余钱为 0,说明他已经平账了,可以不管他了;
2、谁的钱是正数,说明他需要还给别人多少钱;
3、谁的钱是负数,说明他需要等着别人还钱。
按照解题思路求出一个数组 money[] ,它保存着等待平账的账户,已经不包含 0 的情况。
比如 money = {5, -2, -3, 1, 4, -5} ,那么我们就得计算让这个数组全部变为 0 的最少步骤。
我们遍历把 5 全给 -2 或 -3 或 1、…的情况,计算不同情况下的步骤。
比如把 5 全部给 -2 ,此时 money 变成 {0, 3, -3, 1, 4, 5},于是我们就把问题缩减为对 {3, -3, 1, 4, 5} 求最少步骤。
其实就是穷举所有情况并找出步骤最少的。
3、解题代码
class Solution {
private int ans = Integer.MAX_VALUE;
public int minTransfers(int[][] transactions) {
// map(0,3) 表示 0 号人有 3 元
Map<Integer, Integer> account = new HashMap<>();
List<Integer> money = new ArrayList<>();
// 统计所有人的账上余额
for (int[] transaction : transactions) {
if (!account.containsKey(transaction[0])) { // 没有transaction[0]
// 0 借钱给 1,所以 0 的钱减少了,1 的钱增加了
account.put(transaction[0], -transaction[2]);
if (!account.containsKey(transaction[1])){ // 没有 transaction[1]
account.put(transaction[1], transaction[2]);
}else { // // 有 transaction[1]
account.put(transaction[1], account.get(transaction[1]) + transaction[2]);
}
} else { // 有 transaction[0]
account.put(transaction[0], account.get(transaction[0]) - transaction[2]);
if (!account.containsKey(transaction[1])){ // 没有 transaction[1]
account.put(transaction[1], transaction[2]);
}else { // // 有 transaction[1]
account.put(transaction[1], account.get(transaction[1]) + transaction[2]);
}
}
}
// 统计当前账务没有平的钱
for (Integer person : account.keySet()) {
if (account.get(person) != 0) {
money.add(account.get(person));
}
}
// 此时,就对剩余的人直接进行债务抵消
dfs(0, 0, money);
return ans;
}
/**
* 对 money 进行平债务
*
* @param start 即将作为
* @param count 进入 dfs 前,已经操作了多少次
* @param money 账户列表
* @return
*/
private void dfs(int start, int count, List<Integer> money) {
// 不管有没有平账完,如果此时的次数比其他方案的最小值还大,说明没有继续下去的必要了
if (count > ans) return;
while (start < money.size() && money.get(start) == 0) {
// 债务已经平了的就不再处理
start++;
}
if (start == money.size()) {
// 所有债务都平了,现在更新一下操作数
ans = Math.min(ans, count);
return;
}
// dfs 函数核心逻辑
// 遍历把 start 账户的钱全给后面某一个的所有情况
for (int i = start + 1; i < money.size(); i++) {
// 符号相反才能平账,不然帐越来越平不了
if (money.get(start) * money.get(i) < 0) {
money.set(i, money.get(i) + money.get(start)); // 拿 start 的余额对冲账户 i 的余额
dfs(start + 1, count + 1, money); // 接着继续平下一个账户
money.set(i, money.get(i) - money.get(start)); // 恢复原样,接着尝试下一种情况
}
}
}
}