非足额硬币组合下支付金额的最小损失求解算法问询
关于非标准硬币系统下最小损失支付的解法
这是个非常典型的硬币组合优化问题!先给你理清楚前因后果和可行的解法:
先聊聊你提到的「简单方法」
你一开始写的代码是贪心算法的实现:
for i, j in zip(coins, needed): if amount >= 2*i: j = amount // i amount = amount - j * i print(i ," : ", j)
这种方法只在「canonical硬币系统」下有效——也就是像美元常用的1、5、10、50这类面额,贪心选最大面额能得到最优解。但一旦硬币系统不是canonical(比如你举的100、50、20),贪心就彻底失效了:比如要付98美元时,贪心会先选50(因为98<100),剩下48再选两个20,最后剩8凑不出来,连可行解都找不到,更别说最优的「付100亏2」了。
那通用解法是什么?
你的核心需求是:找到≥目标金额的最小支付额(即损失最小),同时这个支付额能被给定的硬币面额凑出来。这里有两种主流思路,取决于你的问题规模:
1. 暴力枚举(适合极小规模场景)
如果硬币面额数量很少、目标金额不大,可以直接枚举所有可能的硬币组合,计算对应的支付额,筛选出≥目标且损失最小的组合。但这种方法时间复杂度极高,面额多一点就会慢到没法用,只能当玩具方案。
2. 动态规划(通用高效解法)
这是处理这类组合优化问题的标准方案,能覆盖所有硬币系统的情况。我们可以通过动态规划数组记录「凑出某金额所需的最小硬币数」,然后在≥目标金额的范围内找损失最小的可行金额。
举个针对你例子的Python实现:
def find_min_loss_payment(target, coins): # 设定上限:目标金额+最大面额,超过这个的话肯定有更小损失的方案 max_possible = target + max(coins) # dp[x]表示凑出x元需要的最少硬币数,初始设为无穷大(不可行) dp = [float('inf')] * (max_possible + 1) dp[0] = 0 # 凑0元需要0个硬币 # 填充dp数组 for amount in range(1, max_possible + 1): for coin in coins: if amount >= coin and dp[amount - coin] + 1 < dp[amount]: dp[amount] = dp[amount - coin] + 1 # 找损失最小的可行支付额 min_loss = float('inf') best_payment = None for amount in range(target, max_possible + 1): if dp[amount] != float('inf'): current_loss = amount - target if current_loss < min_loss: min_loss = current_loss best_payment = amount if not best_payment: return None # 理论上只要有大面额就不会出现这种情况 # 回溯找用了哪些硬币 coins_used = {} remaining = best_payment while remaining > 0: for coin in coins: if remaining >= coin and dp[remaining - coin] == dp[remaining] - 1: coins_used[coin] = coins_used.get(coin, 0) + 1 remaining -= coin break return coins_used, min_loss # 测试你的例子 target = 98 coins = [100, 50, 20] result = find_min_loss_payment(target, coins) if result: used, loss = result print(f"最优方案:支付{sum(k*v for k,v in used.items())}美元({used}),损失{loss}美元")
运行后会输出:最优方案:支付100美元({100: 1}),损失2美元,正好是你想要的结果。
总结一下
- 贪心算法只在特定的「标准硬币系统」下有用,非标准系统直接放弃;
- 动态规划是通用解法,能高效找到最小损失的支付组合;
- 暴力枚举只适合极小规模的场景,实际开发中几乎不用。
内容的提问来源于stack exchange,提问作者sboda
相关产品推荐
相关产品推荐

