【LeetCode(Java) - 465】最优账单平衡

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));  // 恢复原样,接着尝试下一种情况
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_29051413/article/details/108550213