确定硬币组合的算法
Posted
技术标签:
【中文标题】确定硬币组合的算法【英文标题】:Algorithm to determine coin combinations 【发布时间】:2011-08-19 07:47:26 【问题描述】:我最近遇到了一个编程算法提示,我不知道该怎么做。我以前从来没有真正写过算法,所以我在这方面有点新手。
这个问题是写一个程序来确定所有可能的硬币组合,以便收银员根据硬币价值和硬币数量作为零钱返还。例如,一种货币可能有 4 个硬币:2 美分、6 美分、10 美分和 15 美分硬币。这个等于 50 美分的组合有多少种?
我使用的语言是 C++,虽然这并不重要。
编辑:这是一个更具体的编程问题,但我将如何分析 C++ 中的字符串以获取硬币值?它们是在文本文档中给出的,例如
4 2 6 10 15 50
(本例中的数字与我给出的示例相对应)
【问题讨论】:
这个问题有一些有用的答案; ***.com/questions/1106929/… 你需要知道实际的硬币组合,还是只知道它们的数量? 【参考方案1】:这个问题被称为硬币找零问题。详情请查看this 和this。此外,如果您在 Google 上搜索“硬币找零”或“动态编程硬币找零”,您将获得许多其他有用的资源。
【讨论】:
【参考方案2】:这是Java中的递归解决方案:
// Usage: int[] denoms = new int[] 1, 2, 5, 10, 20, 50, 100, 200 ;
// System.out.println(ways(denoms, denoms.length, 200));
public static int ways(int denoms[], int index, int capacity)
if (capacity == 0) return 1;
if (capacity < 0 || index <= 0 ) return 0;
int withoutItem = ways(denoms, index - 1, capacity);
int withItem = ways(denoms, index, capacity - denoms[index - 1]);
return withoutItem + withItem;
【讨论】:
【参考方案3】:这看起来有点像分区,只是你没有使用 1:50 中的所有整数。它似乎也类似于装箱问题,但略有不同:
Wikipedia: Partition (Number Theory) Wikipedia: Bin packing problem Wolfram Mathworld: Partiton其实想了想是an ILP,所以NP-hard。
我建议一些动态编程方法。基本上,您将定义一个值“余数”并将其设置为您的目标(例如 50)。然后,在每一步,您都将执行以下操作:
-
找出最大的硬币可以放入剩余部分中
考虑如果您 (A) 包含该硬币或 (B) 未包含该硬币会发生什么情况。
对于每个场景,递归。
所以如果余数是 50 并且最大的硬币值 25 和 10,您将分为两种情况:
1. Remainder = 25, Coinset = 1x25
2. Remainder = 50, Coinset = 0x25
下一步(针对每个分支)可能如下所示:
1-1. Remainder = 0, Coinset = 2x25 <-- Note: Remainder=0 => Logged
1-2. Remainder = 25, Coinset = 1x25
2-1. Remainder = 40, Coinset = 0x25, 1x10
2-2. Remainder = 50, Coinset = 0x25, 0x10
每个分支都会分成两个分支,除非:
余数为 0(在这种情况下您将记录它) 余数小于最小的硬币(在这种情况下你会丢弃它) 没有更多的硬币了(在这种情况下,您将丢弃它,因为剩余 != 0)【讨论】:
您对如何解决问题的实际描述是死板的,但其余部分存在一些问题:您描述的方法不是 DP,而是普通递归(这是 DP 的正确方法假设我们想写出完整的所有解决方案集,在这里不会给我们买任何东西)。离散解的任何问题都可以表述为 ILP——这并不意味着 NP-hardness。我可以看到与装箱无关。 实际上,我认为我误读了这个问题——似乎 OP 只想知道可以进行更改的不同方式的 number 个。在这种情况下,DP 确实是可能且有用的:对于硬币大小 c 和零钱 a 的每种组合,您可以记录使用最多 c 大小的硬币可以制作 a 的不同方式的数量。 (需要二维 DP 矩阵以避免重复计算解决方案。)【参考方案4】:如果你有 15、10、6 和 2 美分的硬币,你需要找出有多少种不同的方式可以达到 50 分
仅使用 10、6 和 2 计算您必须通过多少种不同的方式达到 50 个 仅使用 10、6 和 2 计算您有多少种不同的方法可以达到 50-15 计算仅使用 10、6 和 2 达到 50-15*2 有多少种不同的方法 计算仅使用 10、6 和 2 达到 50-15*3 有多少种不同的方法 总结所有这些当然不同的结果(第一个我没有使用 15c 硬币,第二个我使用了一个,第三个两个,第四个三个)。因此,您基本上可以将问题拆分为更小的问题(可能更小的数量和更少的硬币)。当你只有一种硬币类型时,答案当然是微不足道的(要么你不能完全达到规定的数量,要么你可以通过唯一可能的方式)。
此外,您还可以通过使用 memoization 来避免重复相同的计算,例如,仅使用 [6, 2] 达到 20 的方式的数量不取决于是否已经使用 15+15 或 10 达到了已支付的 30 +10+10,所以较小问题 (20, [6, 2]) 的结果可以 存储和重复使用。
在Python中这个想法的实现如下
cache =
def howmany(amount, coins):
prob = tuple([amount] + coins) # Problem signature
if prob in cache:
return cache[prob] # We computed this before
if amount == 0:
return 1 # It's always possible to give an exact change of 0 cents
if len(coins) == 1:
if amount % coins[0] == 0:
return 1 # We can match prescribed amount with this coin
else:
return 0 # It's impossible
total = 0
n = 0
while n * coins[0] <= amount:
total += howmany(amount - n * coins[0], coins[1:])
n += 1
cache[prob] = total # Store in cache to avoid repeating this computation
return total
print howmany(50, [15, 10, 6, 2])
【讨论】:
【参考方案5】:至于您问题的第二部分,假设您在文件coins.txt
中有该字符串:
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>
int main()
std::ifstream coins_file("coins.txt");
std::vector<int> coins;
std::copy(std::istream_iterator<int>(coins_file),
std::istream_iterator<int>(),
std::back_inserter(coins));
现在向量coins
将包含可能的硬币值。
【讨论】:
【参考方案6】:对于这么少的硬币,你可以写一个简单的蛮力解决方案。
类似这样的:
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
vector<int> v;
int solve(int total, int * coins, int lastI)
if (total == 50)
for (int i = 0; i < v.size(); i++)
cout << v.at(i) << ' ';
cout << "\n";
return 1;
if (total > 50) return 0;
int sum = 0;
for (int i = lastI; i < 6; i++)
v.push_back(coins[i]);
sum += solve(total + coins[i], coins, i);
v.pop_back();
return sum;
int main()
int coins[6] = 2, 4, 6, 10, 15, 50;
cout << solve(0, coins, 0) << endl;
打印所有可能组合的非常肮脏的蛮力解决方案。
这是一个非常著名的问题,因此请尝试阅读其他人提供的更好的解决方案。
【讨论】:
【参考方案7】:下面是一种相当愚蠢的方法。您构建一个映射“价值 X 的硬币被使用 Y 次”,然后枚举所有可能的组合并只选择那些总和所需的总和。显然,对于每个值 X,您必须检查从 0 到所需总和的 Y。这会很慢,但会解决您的任务。
【讨论】:
【参考方案8】:和the knapsack problem很像
【讨论】:
我不这么认为。在背包问题中,你试图最大化价值,但在这种情况下你没有价值,你不是试图最大化任何东西,而是准确地达到给定的极限。【参考方案9】:您基本上必须解出以下方程:50 = a*4 + b*6 + c*10 + d*15,其中未知数是 a,b,c,d。例如,您可以为每个变量计算 d = (50 - a*4 - b*6 - c*10)/15 等等。然后,您开始给 d 所有可能的值(您应该从具有最小可能值的那个开始,这里是 d):0,1,2,3,4 然后根据当前的情况开始给 c 所有可能的值d的值等等。
【讨论】:
【参考方案10】:向后排序列表:[15 10 6 4 2]
现在 50 ct 的解决方案可以包含 15 ct 或不包含。 所以解决方案的数量是使用 [10 6 4 2] 的 50 ct 解决方案的数量(不再考虑 15 ct 硬币)加上使用 35 ct (=50ct - 15ct) 的解决方案数量[15 10 6 4 2]。对两个子问题重复该过程。
【讨论】:
【参考方案11】:算法是解决问题的过程,它不必使用任何特定的语言。
首先计算输入:
typedef int CoinValue;
set<CoinValue> coinTypes;
int value;
和输出:
set< map<CoinValue, int> > results;
先解决你能想到的最简单的情况:
coinTypes = 1 ; // only one type of coin worth 1 cent
value = 51;
结果应该是:
results = [1 : 51] ; // only one solution, 51 - 1 cent coins
你会如何解决以上问题?
这个怎么样:
coinTypes = 2 ;
value = 51;
results = ; // there is no solution
这个呢?
coinTypes = 1, 2 ;
value = 4 ;
results = [2: 2], [2: 1, 1: 2], [1: 4] ; // the order I put the solutions in is a hint to how to do the algorithm.
【讨论】:
【参考方案12】:Scala 中基于algorithmist.com 资源的递归解决方案:
def countChange(money: Int, coins: List[Int]): Int =
if (money < 0 || coins.isEmpty) 0
else if (money == 0) 1
else countChange(money, coins.tail) + countChange(money - coins.head, coins)
【讨论】:
【参考方案13】:另一个 Python 版本:
def change(coins, money):
return (
change(coins[:-1], money) +
change(coins, money - coins[-1])
if money > 0 and coins
else money == 0
)
【讨论】:
以上是关于确定硬币组合的算法的主要内容,如果未能解决你的问题,请参考以下文章