确定硬币组合的算法

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
    )

【讨论】:

以上是关于确定硬币组合的算法的主要内容,如果未能解决你的问题,请参考以下文章

算法学习:动态规划

如何计算硬币问题的可能组合

硬币面值组合

硬币组合

用动态规化或者深度优先算法来数2英镑有多少种组合方法

所有可能的硬币组合