Google 面试题:最优交易次数

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占用的大小。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值