【问题标题】: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 

(本例中的数字与我给出的示例相对应)

【问题讨论】:

标签: c++ algorithm combinations combinatorics


【解决方案1】:

这个问题被称为硬币找零问题。详情请查看thisthis。此外,如果您在 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 中的所有整数。它似乎也类似于装箱问题,但略有不同:

      其实想了想是an ILP,所以NP-hard。

      我建议一些动态编程方法。基本上,您将定义一个值“余数”并将其设置为您的目标(例如 50)。然后,在每一步,您都将执行以下操作:

      1. 找出最大的硬币可以放入剩余部分中
      2. 考虑如果您 (A) 包含该硬币或 (B) 未包含该硬币会发生什么情况。
      3. 对于每个场景,递归。

      所以如果余数是 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】:

              【讨论】:

              • 我不这么认为。在背包问题中,你试图最大化价值,但在这种情况下你没有价值,你不是试图最大化任何东西,而是准确地达到给定的极限。
              【解决方案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
                          )
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2016-01-25
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2021-03-12
                        • 1970-01-01
                        相关资源
                        最近更新 更多