news 2026/7/14 23:58:45

LeetCode 465 最优账单平衡

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 465 最优账单平衡


文章目录

    • 摘要
    • 描述
    • 题解答案
      • 计算每个人的净资产
      • 在净资产数组上做最少匹配
    • 题解代码分析
      • 核心逻辑拆解一下
        • 为什么可以忽略已经为 0 的人?
        • 为什么只找符号相反的?
        • 剪枝为什么成立?
    • 示例测试及结果
      • 示例 1
        • 分析过程
      • 示例 2
    • 时间复杂度
    • 空间复杂度
    • 总结

摘要

《最优账单平衡》这道题,很多人第一次看到都会有点懵:
不就是转账吗,为什么会是困难题?

真正难的地方不在“算钱”,而在于如何用最少的转账次数,把一堆复杂的债务关系彻底清零
这类问题在现实中非常常见,比如:

  • 多人 AA 聚餐之后的结算
  • 团队项目里垫资、报销、代付
  • 公司内部多部门费用对冲

题目本身不复杂,但如果直接模拟转账,很容易陷入组合爆炸。
正确的解法核心只有一句话:

先把问题化简,再用回溯暴力,但要暴力得聪明。

描述

题目给了我们一组交易transactions,每一条形如:

[from, to, amount]

表示:
fromto转了amount的钱。

最终目标是:
在不改变每个人最终盈亏结果的前提下,用最少的转账次数,把所有人的账清干净。

有几个关键约束需要先想清楚:

  1. 不关心原始交易顺序
  2. 只关心“每个人最终是多收了钱,还是多付了钱”
  3. 转账次数越少越好,金额大小无所谓

换句话说,我们不是要“复现原交易”,而是要重排债务关系

题解答案

整体思路可以分成两步。

计算每个人的净资产

我们先不管怎么转账,只统计结果:

  • 转出的钱:记为负数
  • 转入的钱:记为正数

最后每个人只会剩下一个数:

  • 正数:别人欠他钱
  • 负数:他欠别人钱
  • 0:已经结清,可以直接忽略

这一步非常关键,它能把问题规模大幅缩小。

在净资产数组上做最少匹配

接下来就变成了一个更抽象的问题:

给你一组正负数,正数和负数总和为 0,
用最少的“配对抵消”次数,让所有数都变成 0。

这个问题非常适合用回溯 + 剪枝来解决:

  • 每次找一个还没清零的债务
  • 尝试和后面符号相反的债务进行抵消
  • 把这一步算作一次转账
  • 递归处理剩余部分
  • 取最小次数

题解代码分析

下面是完整 Swift 实现,逻辑清晰、可以直接运行。

classSolution{funcminTransfers(_transactions:[[Int]])->Int{varbalance=[Int:Int]()// 统计每个人的净资产fortintransactions{letfrom=t[0]letto=t[1]letamount=t[2]balance[from,default:0]-=amount balance[to,default:0]+=amount}// 只保留不为 0 的债务vardebts=balance.values.filter{$0!=0}returndfs(&debts,0)}privatefuncdfs(_debts:inout[Int],_start:Int)->Int{// 跳过已经结清的whilestart<debts.count&&debts[start]==0{returndfs(&debts,start+1)}ifstart==debts.count{return0}varminCount=Int.maxforiin(start+1)..<debts.count{// 只尝试符号相反的进行抵消ifdebts[start]*debts[i]<0{letoriginal=debts[i]debts[i]+=debts[start]minCount=min(minCount,1+dfs(&debts,start+1))debts[i]=original// 剪枝:如果正好抵消,没必要再试别的iforiginal+debts[start]==0{break}}}returnminCount}}

核心逻辑拆解一下

为什么可以忽略已经为 0 的人?

因为他们已经“账平了”,不需要再参与任何转账。
这一步对性能提升非常明显。

为什么只找符号相反的?
  • 一个是欠钱(负数)
  • 一个是收钱(正数)

只有这样才能发生真实有效的“抵消”。
两个正数或者两个负数,永远解决不了问题。

剪枝为什么成立?
iforiginal+debts[start]==0{break}

这表示:
当前这次配对已经是完美抵消,再继续尝试别的组合,只会增加转账次数,不可能更优。

示例测试及结果

示例 1

letsolution=Solution()lettransactions=[[0,1,10],[2,0,5]]print(solution.minTransfers(transactions))
分析过程
  • 0:-10 + 5 = -5
  • 1:+10
  • 2:-5

债务数组变成:

[-5, 10, -5]

最优方案:

  • 1 给 0 转 5
  • 1 给 2 转 5

总共2 次转账

输出:

2

示例 2

lettransactions=[[0,1,10],[1,0,10]]print(solution.minTransfers(transactions))

两笔交易完全抵消,所有人最终余额都是 0。

输出:

0

时间复杂度

这道题本质上是一个NP 难问题

  • 最坏情况下,需要尝试所有债务组合
  • 回溯的时间复杂度接近O(n!)

但题目限制了参与人数,一般不会超过 12 个非零债务节点。
再加上大量剪枝,实际运行非常快。

空间复杂度

主要消耗在:

  • debts数组
  • 递归调用栈

空间复杂度为:

O(n)

总结

《最优账单平衡》这道题的价值不在于“写代码有多复杂”,而在于建模能力

  1. 把原始交易压缩成“净资产”
  2. 把业务问题抽象成“正负数抵消”
  3. 用回溯解决“最少操作次数”问题
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/1 11:24:56

GO 教程

Go linker&#xff08;go tool link&#xff09;参数速查表一、构建标识 / 版本 / 信息注入&#xff08;最常用 ⭐⭐⭐&#xff09;参数说明常用-B note设置 ELF Build ID / Mach-O UUID⭐-buildid id设置 Go build ID&#xff08;-buildid 可禁用&#xff09;⭐⭐-X importpat…

作者头像 李华
网站建设 2026/7/14 23:57:07

双碳目标下综合能源系统低碳运行优化调度Matlab实现

双碳目标下综合能源系统低碳运行优化调度Matlab程序 包含光伏、风电、热电联产、燃气锅炉、电锅炉、电储能、碳捕集设备&#xff0c;考虑碳交易 以系统运行成本最小为目标函数 采用Yalmip&#xff0b;Cplex求解在双碳目标的大背景下&#xff0c;综合能源系统的低碳运行优化调度…

作者头像 李华
网站建设 2026/7/13 13:06:19

springboot+vue校园二手闲置物品拍卖系统 人脸识别

目录摘要开发技术核心代码参考示例1.建立用户稀疏矩阵&#xff0c;用于用户相似度计算【相似度矩阵】2.计算目标用户与其他用户的相似度总结源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 校园二手闲置物品拍卖系统结合SpringBoo…

作者头像 李华