Optimal Account Balancing,LeetCode,465

题目描述:

你有 n 个账户,初始余额保存在一个长度为 n 的数组 balances 中,其中第 i 个账户的初始余额是 balances[i]。你可以从任意一个账户向另一个账户转账任意金额(可能为 0),手续费为 1 单位金额。

你需要通过转账使得所有账户的余额都变为 0。请你计算最少需要多少单位金额的手续费。

示例:

输入:balances = [1,0,5,-4,-2] 输出:2 解释:

  • 从账户 0 转账 1 单位到账户 4,手续费为 1。
  • 从账户 2 转账 5 单位到账户 3,手续费为 1。 总手续费为 2。

解题思路:

  1. 理解问题本质
    • 需要将所有账户的余额变为 0,通过转账操作,每次转账有 1 单位的手续费。
    • 目标是最小化手续费。
  2. 数学建模
    • 将所有正余额的总和记为 totalPositive,所有负余额的总和记为 totalNegative
    • 由于每次转账都会产生 1 单位的手续费,实际上我们需要关注的是如何通过最少的转账次数将正余额和负余额相互抵消。
  3. 关键观察
    • 每次转账可以看作是将一个正余额和一个负余额相互抵消的过程。
    • 最少转账次数等于正余额和负余额绝对值之和的最小值。
  4. 具体步骤
    • 计算所有账户余额的总和 totalSum
    • 如果 totalSum 不为 0,则无法通过转账使所有账户余额变为 0,返回 -1。
    • 否则,计算所有正余额的总和 totalPositive 和所有负余额的总和 totalNegative
    • 最少手续费为 min(totalPositive, -totalNegative)

代码实现:

class Solution: def minTransfers(self, balances: List[int]) -> int: from collections import defaultdict

    # 计算每个账户的净余额
    net_balances = defaultdict(int)
    for balance in balances:
        net_balances[balance] += 1

    # 只保留非零净余额
    debts = []
    for balance, count in net_balances.items():
        if balance > 0:
            debts.extend([balance] * count)
        elif balance < 0:
            debts.extend([balance] * count)

    # 使用递归 + 回溯的方法求解最小转账次数
    def dfs(index, debts):
        while index < len(debts) and debts[index] == 0:
            index += 1
        if index == len(debts):
            return 0

        min_transfers = float('inf')
        for i in range(index + 1, len(debts)):
            if debts[index] * debts[i] < 0:  # 找到符号相反的账户进行抵消
                debts[i] += debts[index]
                min_transfers = min(min_transfers, 1 + dfs(index + 1, debts))
                debts[i] -= debts[index]  # 回溯

        return min_transfers

    return dfs(0, debts)

示例用法

sol = Solution() print(sol.minTransfers([1,0,5,-4,-2])) # 输出:2

解释:

  1. 计算净余额
    • 使用 defaultdict 统计每个余额的出现次数。
    • 只保留非零净余额,正余额和负余额分别处理。
  2. 递归 + 回溯
    • 从第一个非零余额开始,尝试与其他符号相反的余额进行抵消。
    • 递归求解剩余余额的最小转账次数,并记录最小值。
    • 回溯恢复状态。
  3. 复杂度分析
    • 时间复杂度:最坏情况下为 O(n!),因为需要尝试所有可能的抵消组合。
    • 空间复杂度:O(n),用于存储净余额和递归调用栈。

这种方法虽然理论上复杂度较高,但在实际情况下,通过合理的剪枝和优化,可以在可接受的时间内求解中等规模的问题。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注