【问题标题】:How to find all combinations of coins when given some dollar value [closed]给定一些美元价值时如何找到所有硬币组合[关闭]
【发布时间】:2010-11-09 13:35:46
【问题描述】:

我找到了几个月前为面试准备而编写的一段代码。

根据我的评论,它试图解决这个问题:

给定一些以美分为单位的美元价值(例如 200 = 2 美元,1000 = 10 美元),找出构成美元价值的所有硬币组合。 只允许使用便士 (1 美分)、镍 (5 美分)、一角硬币 (10 美分) 和 25 美分 (25 美分)。

例如,如果给出 100,答案应该是:

4 quarter(s) 0 dime(s) 0 nickel(s) 0 pennies  
3 quarter(s) 1 dime(s) 0 nickel(s) 15 pennies  
etc.

我相信这可以通过迭代和递归的方式来解决。我的递归解决方案有很多错误,我想知道其他人将如何解决这个问题。这个问题的难点在于使其尽可能高效。

【问题讨论】:

  • @akappa:便士 = 1 美分;镍 = 5 美分;一角钱 = 10 美分;季度 = 25 美分 :)
  • @John T:打码高尔夫?我从来没有听说过这个词!无论如何,我希望看到一些有趣的答案,因为 SO 社区可以解决任何问题
  • 我回家后也会尝试发布我的答案...仍在工作,我不应该在 SO 上花费太多时间。
  • @blee 代码高尔夫是指使用您选择的编程语言以尽可能少的字符解决问题。以下是本网站上已完成的一些操作:stackoverflow.com/search?q=code+golf

标签: algorithm recursion puzzle coin-change


【解决方案1】:

很久以前我研究过一次,你可以阅读我的little write-up on it。这是Mathematica source

通过使用生成函数,您可以获得问题的封闭形式的常数时间解。 Graham、Knuth 和 Patashnik 的 具体数学 就是为此而写的书,其中包含对该问题的相当广泛的讨论。本质上,您定义了一个多项式,其中第 n 个系数是为 n 美元进行更改的方式的数量。

文章的第 4-5 页展示了如何使用 Mathematica(或任何其他方便的计算机代数系统)在几秒钟内用三行代码计算出 10^10^6 美元的答案。

(这已经足够久远了,在 75Mhz Pentium 上只需要几秒钟......)

【讨论】:

  • 好答案,但有一些小问题:请注意 (1) 这给出了 number 种方式,而由于某种原因,该问题要求提供所有方式的实际集合。当然,不可能在多项式时间内找到集合,因为输出本身具有超多项式多项式 (2) 生成函数是否是“封闭形式”是有争议的(参见 Herbert Wilf 的精彩著作 Generatingfunctionology : math.upenn.edu/~wilf/DownldGF.html) 如果你的意思是像 (1+√5)^n 这样的表达式,计算需要 Ω(log n) 时间,而不是恒定时间。
  • 对动态规划的温和介绍。另外,我鼓励任何有序列问题的人阅读generatingfunctionology
  • 非常感谢安德鲁...这个解释帮助了我很多...在下面发布 scala 函数..如果有人需要它
  • 我相信一开始的问题需要稍微修正,因为它问的是“......使用 1 美分、10 美分、25 美分、50 美分和 100 美分硬币?”但是随后的文章将集合a 定义为fa = {1,5,10,25,50,100} 的域。美分硬币列表中应该有一个 5-。否则写得很棒,谢谢!
  • @rbrtl 哇,你是对的,感谢您注意到这一点!我会更新它...
【解决方案2】:

注意:这里只显示路数。

Scala 函数:

def countChange(money: Int, coins: List[Int]): Int =
  if (money == 0) 1
  else if (coins.isEmpty || money < 0) 0
  else countChange(money - coins.head, coins) + countChange(money, coins.tail)

【讨论】:

  • 真的有一种方法可以改变0吗?我想没有办法做到这一点。
  • 源于多项式解的数量n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money。因此对于money=0coins=List(1,2,5,10),组合(n1, n2, n3, n4) 的计数为1,解决方案为(0, 0, 0, 0)
  • 我无法理解为什么这个实现有效。有人可以解释一下背后的算法吗?
  • 这绝对是coursera scala课程练习1第3题的准确答案。
  • 我相信,如果money == 0coins.isEmpty,它不应该算作一个sol'n。因此,如果 coins.isEmpty || money &lt; 0 条件首先被 ck'd,则算法可能会更好地服务。
【解决方案3】:

我倾向于递归解决方案。您有一些面额列表,如果最小的面额可以平分任何剩余的货币金额,这应该可以正常工作。

基本上,您从最大面额到最小面额。
递归,

  1. 您当前有一个要填写的总数,以及一个最大的面额(还剩 1 个以上)。 如果只剩下 1 个面额,则只有一种方法可以填充总数。您可以使用当前面额的 0 到 k 个副本,这样 k * cur denomination
  2. 对于 0 到 k,使用修改后的总和新的最大面额调用函数。
  3. 将结果从 0 加到 k。这就是您可以从当前面额向下填充总数的多少种方式。返回此号码。

这是我提出的问题的 python 版本,售价 200 美分。我有 1463 种方式。此版本会打印所有组合和最终总数。

#!/usr/bin/python

# find the number of ways to reach a total with the given number of combinations

cents = 200
denominations = [25, 10, 5, 1]
names = {25: "quarter(s)", 10: "dime(s)", 5 : "nickel(s)", 1 : "pennies"}

def count_combs(left, i, comb, add):
    if add: comb.append(add)
    if left == 0 or (i+1) == len(denominations):
        if (i+1) == len(denominations) and left > 0:
           if left % denominations[i]:
               return 0
           comb.append( (left/denominations[i], demoninations[i]) )
           i += 1
        while i < len(denominations):
            comb.append( (0, denominations[i]) )
            i += 1
        print(" ".join("%d %s" % (n,names[c]) for (n,c) in comb))
        return 1
    cur = denominations[i]
    return sum(count_combs(left-x*cur, i+1, comb[:], (x,cur)) for x in range(0, int(left/cur)+1))

count_combs(cents, 0, [], None)

【讨论】:

  • 没跑过,但通过你的逻辑,它是有道理的:)
  • 您可以将函数的最后两行替换为“return sum(count_combs(...) for ...)” - 这样列表根本不会被具体化。 :)
  • 感谢您的提示。我一直对收紧代码的方法很感兴趣。
  • 正如another question 中所讨论的,如果denominations 的列表没有1 作为最后一个值,则此代码将给出不正确的输出。您可以在最里面的 if 块中添加少量代码来修复它(正如我在对另一个问题的回答中所描述的那样)。
【解决方案4】:

Scala 函数:

def countChange(money: Int, coins: List[Int]): Int = {

def loop(money: Int, lcoins: List[Int], count: Int): Int = {
  // if there are no more coins or if we run out of money ... return 0 
  if ( lcoins.isEmpty || money < 0) 0
  else{
    if (money == 0 ) count + 1   
/* if the recursive subtraction leads to 0 money left - a prefect division hence return count +1 */
    else
/* keep iterating ... sum over money and the rest of the coins and money - the first item and the full set of coins left*/
      loop(money, lcoins.tail,count) + loop(money - lcoins.head,lcoins, count)
  }
}

val x = loop(money, coins, 0)
Console println x
x
}

【讨论】:

  • 谢谢!这是一个很好的开始。但是,我认为当“钱”开始为 0 时这会失败 :)。
【解决方案5】:

这里有一些绝对简单的 C++ 代码来解决要求显示所有组合的问题。

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("usage: change amount-in-cents\n");
        return 1;
    }

    int total = atoi(argv[1]);

    printf("quarter\tdime\tnickle\tpenny\tto make %d\n", total);

    int combos = 0;

    for (int q = 0; q <= total / 25; q++)
    {
        int total_less_q = total - q * 25;
        for (int d = 0; d <= total_less_q / 10; d++)
        {
            int total_less_q_d = total_less_q - d * 10;
            for (int n = 0; n <= total_less_q_d / 5; n++)
            {
                int p = total_less_q_d - n * 5;
                printf("%d\t%d\t%d\t%d\n", q, d, n, p);
                combos++;
            }
        }
    }

    printf("%d combinations\n", combos);

    return 0;
}

但我对仅计算组合数量的子问题非常感兴趣。我怀疑它有一个封闭形式的方程。

【讨论】:

  • 这肯定是 C,而不是 C++。
  • @George Phillips 你能解释一下吗?
  • 我认为这很简单。基本上,这个想法是迭代所有季度(使用 0,1,2 .. max),然后根据使用的季度等遍历所有角钱。
  • 这个解决方案的缺点是:如果有 50-cent、100-cent、500-cent 硬币,那么我们必须使用 6 级循环......
  • 这很糟糕,如果你有一个动态的面额,或者你想添加另一个面额,那么这将不起作用。
【解决方案6】:

子问题是一个典型的动态规划问题。

/* Q: Given some dollar value in cents (e.g. 200 = 2 dollars, 1000 = 10 dollars),
      find the number of combinations of coins that make up the dollar value.
      There are only penny, nickel, dime, and quarter.
      (quarter = 25 cents, dime = 10 cents, nickel = 5 cents, penny = 1 cent) */
/* A:
Reference: http://andrew.neitsch.ca/publications/m496pres1.nb.pdf
f(n, k): number of ways of making change for n cents, using only the first
         k+1 types of coins.

          +- 0,                        n < 0 || k < 0
f(n, k) = |- 1,                        n == 0
          +- f(n, k-1) + f(n-C[k], k), else
 */

#include <iostream>
#include <vector>
using namespace std;

int C[] = {1, 5, 10, 25};

// Recursive: very slow, O(2^n)
int f(int n, int k)
{
    if (n < 0 || k < 0)
        return 0;

    if (n == 0)
        return 1;

    return f(n, k-1) + f(n-C[k], k); 
}

// Non-recursive: fast, but still O(nk)
int f_NonRec(int n, int k)
{
    vector<vector<int> > table(n+1, vector<int>(k+1, 1));

    for (int i = 0; i <= n; ++i)
    {
        for (int j = 0; j <= k; ++j)
        {
            if (i < 0 || j < 0) // Impossible, for illustration purpose
            {
                table[i][j] = 0;
            }
            else if (i == 0 || j == 0) // Very Important
            {
                table[i][j] = 1;
            }
            else
            {
                // The recursion. Be careful with the vector boundary
                table[i][j] = table[i][j-1] + 
                    (i < C[j] ? 0 : table[i-C[j]][j]);
            }
        }
    }

    return table[n][k];
}

int main()
{
    cout << f(100, 3) << ", " << f_NonRec(100, 3) << endl;
    cout << f(200, 3) << ", " << f_NonRec(200, 3) << endl;
    cout << f(1000, 3) << ", " << f_NonRec(1000, 3) << endl;

    return 0;
}

【讨论】:

  • 您的动态解决方案要求 k 是 C 的长度减去 1。有点混乱。您可以轻松更改它以支持 C 的实际长度。
【解决方案7】:

代码是用Java来解决这个问题的,它也可以工作......这个方法可能不是一个好主意,因为循环太多,但它确实是一个直接的方法。

public class RepresentCents {

    public static int sum(int n) {

        int count = 0;
        for (int i = 0; i <= n / 25; i++) {
            for (int j = 0; j <= n / 10; j++) {
                for (int k = 0; k <= n / 5; k++) {
                    for (int l = 0; l <= n; l++) {
                        int v = i * 25 + j * 10 + k * 5 + l;
                        if (v == n) {
                            count++;
                        } else if (v > n) {
                            break;
                        }
                    }
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        System.out.println(sum(100));
    }
}

【讨论】:

    【解决方案8】:

    这是一个非常古老的问题,但我在 java 中提出了一个递归解决方案,它似乎比其他所有解决方案都小,所以这里 -

     public static void printAll(int ind, int[] denom,int N,int[] vals){
        if(N==0){
            System.out.println(Arrays.toString(vals));
            return;
        }
        if(ind == (denom.length))return;             
        int currdenom = denom[ind];
        for(int i=0;i<=(N/currdenom);i++){
            vals[ind] = i;
            printAll(ind+1,denom,N-i*currdenom,vals);
        }
     }
    

    改进:

      public static void printAllCents(int ind, int[] denom,int N,int[] vals){
            if(N==0){
                if(ind < denom.length) {
                    for(int i=ind;i<denom.length;i++)
                        vals[i] = 0;
                }
                System.out.println(Arrays.toString(vals));
                return;
            }
            if(ind == (denom.length)) {
                vals[ind-1] = 0;
                return;             
            }
    
            int currdenom = denom[ind];
            for(int i=0;i<=(N/currdenom);i++){ 
                    vals[ind] = i;
                    printAllCents(ind+1,denom,N-i*currdenom,vals);
            }
         }
    

    【讨论】:

      【解决方案9】:

      让 C(i,J) 使用集合 J 中的值生成 i 美分的组合集合。

      你可以这样定义C:

      (first(J) 以一种确定的方式获取集合中的一个元素)

      事实证明,这是一个非常递归的函数……如果你使用记忆化,效率相当高;)

      【讨论】:

      • 是的,这(某种意义上的“动态规划”)将是最佳解决方案。
      • 你是对的:把 J 作为一个列表而不是一个集合:然后 first(J) 给你第一个元素, J \ first(J) 给你列表的其余部分.
      • 这是什么形式的数学?
      【解决方案10】:

      semi-hack 解决独特组合问题 - 强制降序:

      $denoms = [1,5,10,25]
      def all_combs(sum,last)
        如果 sum == 0 则返回 1
        返回 $denoms.select{|d| d &le sum && d &le last}.inject(0) {|total,denom|
                 总计+all_combs(sum-denom,denom)}
      结尾
      

      这将运行缓慢,因为它不会被记忆,但你明白了。

      【讨论】:

        【解决方案11】:
        # short and sweet with O(n) table memory    
        
        #include <iostream>
        #include <vector>
        
        int count( std::vector<int> s, int n )
        {
          std::vector<int> table(n+1,0);
        
          table[0] = 1;
          for ( auto& k : s )
            for(int j=k; j<=n; ++j)
              table[j] += table[j-k];
        
          return table[n];
        }
        
        int main()
        {
          std::cout <<  count({25, 10, 5, 1}, 100) << std::endl;
          return 0;
        }
        

        【讨论】:

          【解决方案12】:

          这是我在 Python 中的答案。它不使用递归:

          def crossprod (list1, list2):
              output = 0
              for i in range(0,len(list1)):
                  output += list1[i]*list2[i]
          
              return output
          
          def breakit(target, coins):
              coinslimit = [(target / coins[i]) for i in range(0,len(coins))]
              count = 0
              temp = []
              for i in range(0,len(coins)):
                  temp.append([j for j in range(0,coinslimit[i]+1)])
          
          
              r=[[]]
              for x in temp:
                  t = []
                  for y in x:
                      for i in r:
                          t.append(i+[y])
                  r = t
          
              for targets in r:
                  if crossprod(targets, coins) == target:
                      print targets
                      count +=1
              return count
          
          
          
          
          if __name__ == "__main__":
              coins = [25,10,5,1]
              target = 78
              print breakit(target, coins)
          

          示例输出

              ...
              1 ( 10 cents)  2 ( 5 cents)  58 ( 1 cents)  
              4 ( 5 cents)  58 ( 1 cents)  
              1 ( 10 cents)  1 ( 5 cents)  63 ( 1 cents)  
              3 ( 5 cents)  63 ( 1 cents)  
              1 ( 10 cents)  68 ( 1 cents)  
              2 ( 5 cents)  68 ( 1 cents)  
              1 ( 5 cents)  73 ( 1 cents)  
              78 ( 1 cents)  
              Number of solutions =  121
          

          【讨论】:

            【解决方案13】:
            var countChange = function (money,coins) {
              function countChangeSub(money,coins,n) {
                if(money==0) return 1;
                if(money<0 || coins.length ==n) return 0;
                return countChangeSub(money-coins[n],coins,n) + countChangeSub(money,coins,n+1);
              }
              return countChangeSub(money,coins,0);
            }
            

            【讨论】:

              【解决方案14】:

              两者:从高到低遍历所有面额,取其中一个面额,从要求的总数中减去,然后在余数上递归(将可用面额限制为等于或低于当前迭代值。)

              【讨论】:

                【解决方案15】:

                如果货币系统允许,一个简单的greedy algorithm 会尽可能多地获取每种硬币,从价值最高的货币开始。

                否则,需要动态规划来快速找到最优解,因为这个问题本质上是knapsack problem

                例如,如果一个货币系统有硬币:{13, 8, 1},贪婪的解决方案会将 24 的零钱变成{13, 8, 1, 1, 1},但真正的最优解决方案是{8, 8, 8}

                编辑:我认为我们正在以最佳方式进行更改,而不是列出所有更改一美元的方法。我最近的采访询问了如何做出改变,所以我在读完这个问题之前就跳到了前面。

                【讨论】:

                • 问题不一定是 1 美元——它可能是 2 或 23,所以你的解决方案仍然是唯一正确的。
                【解决方案16】:

                我知道这是一个非常古老的问题。我正在寻找正确的答案,但找不到任何简单而令人满意的东西。花了我一些时间,但能够记下一些东西。

                function denomination(coins, original_amount){
                    var original_amount = original_amount;
                    var original_best = [ ];
                
                    for(var i=0;i<coins.length; i++){
                      var amount = original_amount;
                      var best = [ ];
                      var tempBest = [ ]
                      while(coins[i]<=amount){
                        amount = amount - coins[i];
                        best.push(coins[i]);
                      }
                      if(amount>0 && coins.length>1){
                        tempBest = denomination(coins.slice(0,i).concat(coins.slice(i+1,coins.length)), amount);
                        //best = best.concat(denomination(coins.splice(i,1), amount));
                      }
                      if(tempBest.length!=0 || (best.length!=0 && amount==0)){
                        best = best.concat(tempBest);
                        if(original_best.length==0 ){
                          original_best = best
                        }else if(original_best.length > best.length ){
                          original_best = best;
                        }  
                      }
                    }
                    return original_best;  
                  }
                  denomination( [1,10,3,9] , 19 );
                

                这是一个 javascript 解决方案并使用递归。

                【讨论】:

                • 这个解决方案只能找到一个面额。问题是找到“所有”教派。
                【解决方案17】:

                在 Scala 编程语言中,我会这样做:

                 def countChange(money: Int, coins: List[Int]): Int = {
                
                       money match {
                           case 0 => 1
                           case x if x < 0 => 0
                           case x if x >= 1 && coins.isEmpty => 0
                           case _ => countChange(money, coins.tail) + countChange(money - coins.head, coins)
                
                       }
                
                  }
                

                【讨论】:

                  【解决方案18】:

                  这是一个简单的递归算法,它先取一张钞票,然后递归取一张较小的钞票,直到达到总和,然后再取另一张相同面额的钞票,然后再次递归。请参阅下面的示例输出以进行说明。

                  var bills = new int[] { 100, 50, 20, 10, 5, 1 };
                  
                  void PrintAllWaysToMakeChange(int sumSoFar, int minBill, string changeSoFar)
                  {
                      for (int i = minBill; i < bills.Length; i++)
                      {
                          var change = changeSoFar;
                          var sum = sumSoFar;
                  
                          while (sum > 0)
                          {
                              if (!string.IsNullOrEmpty(change)) change += " + ";
                              change += bills[i];
                  
                              sum -= bills[i]; 
                              if (sum > 0)
                              {
                                  PrintAllWaysToMakeChange(sum, i + 1, change);
                              }
                          }
                  
                          if (sum == 0)
                          {
                              Console.WriteLine(change);
                          }
                      }
                  }
                  
                  PrintAllWaysToMakeChange(15, 0, "");
                  

                  打印以下内容:

                  10 + 5
                  10 + 1 + 1 + 1 + 1 + 1
                  5 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
                  5 + 5 + 1 + 1 + 1 + 1 + 1
                  5 + 5 + 5
                  1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
                  

                  【讨论】:

                    【解决方案19】:

                    呃,我现在觉得自己很愚蠢。下面有一个过于复杂的解决方案,我将保留它,因为它毕竟是一个解决方案。一个简单的解决方案是:

                    // Generate a pretty string
                    val coinNames = List(("quarter", "quarters"), 
                                         ("dime", "dimes"), 
                                         ("nickel", "nickels"), 
                                         ("penny", "pennies"))
                    def coinsString = 
                      Function.tupled((quarters: Int, dimes: Int, nickels:Int, pennies: Int) => (
                        List(quarters, dimes, nickels, pennies) 
                        zip coinNames // join with names
                        map (t => (if (t._1 != 1) (t._1, t._2._2) else (t._1, t._2._1))) // correct for number
                        map (t => t._1 + " " + t._2) // qty name
                        mkString " "
                      ))
                    
                    def allCombinations(amount: Int) = 
                     (for{quarters <- 0 to (amount / 25)
                          dimes <- 0 to ((amount - 25*quarters) / 10)
                          nickels <- 0 to ((amount - 25*quarters - 10*dimes) / 5)
                      } yield (quarters, dimes, nickels, amount - 25*quarters - 10*dimes - 5*nickels)
                     ) map coinsString mkString "\n"
                    

                    这是另一个解决方案。该解决方案基于观察到每个硬币都是其他硬币的倍数,因此可以用它们来表示它们。

                    // Just to make things a bit more readable, as these routines will access
                    // arrays a lot
                    val coinValues = List(25, 10, 5, 1)
                    val coinNames = List(("quarter", "quarters"), 
                                         ("dime", "dimes"), 
                                         ("nickel", "nickels"), 
                                         ("penny", "pennies"))
                    val List(quarter, dime, nickel, penny) = coinValues.indices.toList
                    
                    
                    // Find the combination that uses the least amount of coins
                    def leastCoins(amount: Int): Array[Int] =
                      ((List(amount) /: coinValues) {(list, coinValue) =>
                        val currentAmount = list.head
                        val numberOfCoins = currentAmount / coinValue
                        val remainingAmount = currentAmount % coinValue
                        remainingAmount :: numberOfCoins :: list.tail
                      }).tail.reverse.toArray
                    
                    // Helper function. Adjust a certain amount of coins by
                    // adding or subtracting coins of each type; this could
                    // be made to receive a list of adjustments, but for so
                    // few types of coins, it's not worth it.
                    def adjust(base: Array[Int], 
                               quarters: Int, 
                               dimes: Int, 
                               nickels: Int, 
                               pennies: Int): Array[Int] =
                      Array(base(quarter) + quarters, 
                            base(dime) + dimes, 
                            base(nickel) + nickels, 
                            base(penny) + pennies)
                    
                    // We decrease the amount of quarters by one this way
                    def decreaseQuarter(base: Array[Int]): Array[Int] =
                      adjust(base, -1, +2, +1, 0)
                    
                    // Dimes are decreased this way
                    def decreaseDime(base: Array[Int]): Array[Int] =
                      adjust(base, 0, -1, +2, 0)
                    
                    // And here is how we decrease Nickels
                    def decreaseNickel(base: Array[Int]): Array[Int] =
                      adjust(base, 0, 0, -1, +5)
                    
                    // This will help us find the proper decrease function
                    val decrease = Map(quarter -> decreaseQuarter _,
                                       dime -> decreaseDime _,
                                       nickel -> decreaseNickel _)
                    
                    // Given a base amount of coins of each type, and the type of coin,
                    // we'll produce a list of coin amounts for each quantity of that particular
                    // coin type, up to the "base" amount
                    def coinSpan(base: Array[Int], whichCoin: Int) = 
                      (List(base) /: (0 until base(whichCoin)).toList) { (list, _) =>
                        decrease(whichCoin)(list.head) :: list
                      }
                    
                    // Generate a pretty string
                    def coinsString(base: Array[Int]) = (
                      base 
                      zip coinNames // join with names
                      map (t => (if (t._1 != 1) (t._1, t._2._2) else (t._1, t._2._1))) // correct for number
                      map (t => t._1 + " " + t._2)
                      mkString " "
                    )
                    
                    // So, get a base amount, compute a list for all quarters variations of that base,
                    // then, for each combination, compute all variations of dimes, and then repeat
                    // for all variations of nickels.
                    def allCombinations(amount: Int) = {
                      val base = leastCoins(amount)
                      val allQuarters = coinSpan(base, quarter)
                      val allDimes = allQuarters flatMap (base => coinSpan(base, dime))
                      val allNickels = allDimes flatMap (base => coinSpan(base, nickel))
                      allNickels map coinsString mkString "\n"
                    }
                    

                    所以,以 37 个硬币为例:

                    scala> println(allCombinations(37))
                    0 quarter 0 dimes 0 nickels 37 pennies
                    0 quarter 0 dimes 1 nickel 32 pennies
                    0 quarter 0 dimes 2 nickels 27 pennies
                    0 quarter 0 dimes 3 nickels 22 pennies
                    0 quarter 0 dimes 4 nickels 17 pennies
                    0 quarter 0 dimes 5 nickels 12 pennies
                    0 quarter 0 dimes 6 nickels 7 pennies
                    0 quarter 0 dimes 7 nickels 2 pennies
                    0 quarter 1 dime 0 nickels 27 pennies
                    0 quarter 1 dime 1 nickel 22 pennies
                    0 quarter 1 dime 2 nickels 17 pennies
                    0 quarter 1 dime 3 nickels 12 pennies
                    0 quarter 1 dime 4 nickels 7 pennies
                    0 quarter 1 dime 5 nickels 2 pennies
                    0 quarter 2 dimes 0 nickels 17 pennies
                    0 quarter 2 dimes 1 nickel 12 pennies
                    0 quarter 2 dimes 2 nickels 7 pennies
                    0 quarter 2 dimes 3 nickels 2 pennies
                    0 quarter 3 dimes 0 nickels 7 pennies
                    0 quarter 3 dimes 1 nickel 2 pennies
                    1 quarter 0 dimes 0 nickels 12 pennies
                    1 quarter 0 dimes 1 nickel 7 pennies
                    1 quarter 0 dimes 2 nickels 2 pennies
                    1 quarter 1 dime 0 nickels 2 pennies
                    

                    【讨论】:

                      【解决方案20】:

                      This blog entry of mine 解决了来自XKCD comic 的数字的类似背包问题。对 items dict 和 exactcost 值的简单更改也将为您的问题提供所有解决方案。

                      如果问题是找到成本最低的找零,那么对于硬币和目标金额的某些组合,使用尽可能多的最高价值硬币的天真贪心算法很可能会失败。例如,如果有值 1、3 和 4 的硬币;并且目标数量是 6,那么贪心算法可能会建议价值 4、1 和 1 的三个硬币,但很容易看出您可以使用两个硬币,每个硬币的价值为 3。

                      • 稻田。

                      【讨论】:

                        【解决方案21】:
                        public class Coins {
                        
                        static int ac = 421;
                        static int bc = 311;
                        static int cc = 11;
                        
                        static int target = 4000;
                        
                        public static void main(String[] args) {
                        
                        
                            method2();
                        }
                        
                          public static void method2(){
                            //running time n^2
                        
                            int da = target/ac;
                            int db = target/bc;     
                        
                            for(int i=0;i<=da;i++){         
                                for(int j=0;j<=db;j++){             
                                    int rem = target-(i*ac+j*bc);               
                                    if(rem < 0){                    
                                        break;                  
                                    }else{                  
                                        if(rem%cc==0){                  
                                            System.out.format("\n%d, %d, %d ---- %d + %d + %d = %d \n", i, j, rem/cc, i*ac, j*bc, (rem/cc)*cc, target);                     
                                        }                   
                                    }                   
                                }           
                            }       
                        }
                         }
                        

                        【讨论】:

                          【解决方案22】:

                          我在 O'reily 的“Python For Data Analysis”一书中找到了这段简洁的代码。它使用惰性实现和 int 比较,我认为它可以修改为使用小数的其他面额。告诉我它是如何为您工作的!

                          def make_change(amount, coins=[1, 5, 10, 25], hand=None):
                           hand = [] if hand is None else hand
                           if amount == 0:
                           yield hand
                           for coin in coins:
                           # ensures we don't give too much change, and combinations are unique
                           if coin > amount or (len(hand) > 0 and hand[-1] < coin):
                           continue
                           for result in make_change(amount - coin, coins=coins,
                           hand=hand + [coin]):
                           yield result

                          【讨论】:

                            【解决方案23】:

                            这是子涵回答的改进。当面额只有 1 美分时,就会出现大量不必要的循环。

                            它直观且非递归。

                                public static int Ways2PayNCents(int n)
                                {
                                    int numberOfWays=0;
                                    int cent, nickel, dime, quarter;
                                    for (quarter = 0; quarter <= n/25; quarter++)
                                    {
                                        for (dime = 0; dime <= n/10; dime++)
                                        {
                                            for (nickel = 0; nickel <= n/5; nickel++)
                                            {
                                                cent = n - (quarter * 25 + dime * 10 + nickel * 5);
                                                if (cent >= 0)
                                                {
                                                    numberOfWays += 1;
                                                    Console.WriteLine("{0},{1},{2},{3}", quarter, dime, nickel, cent);
                                                }                   
                                            }
                                        }
                                    }
                                    return numberOfWays;            
                                }
                            

                            【讨论】:

                            • 你不能概括这个解决方案,所以例如一个新元素出现在这种情况下你必须添加另一个 for 循环
                            【解决方案24】:

                            简单的 java 解决方案:

                            public static void main(String[] args) 
                            {    
                                int[] denoms = {4,2,3,1};
                                int[] vals = new int[denoms.length];
                                int target = 6;
                                printCombinations(0, denoms, target, vals);
                            }
                            
                            
                            public static void printCombinations(int index, int[] denom,int target, int[] vals)
                            {
                              if(target==0)
                              {
                                System.out.println(Arrays.toString(vals));
                                return;
                              }
                              if(index == denom.length) return;   
                              int currDenom = denom[index];
                              for(int i = 0; i*currDenom <= target;i++)
                              {
                                vals[index] = i;
                                printCombinations(index+1, denom, target - i*currDenom, vals);
                                vals[index] = 0;
                              }
                            }
                            

                            【讨论】:

                              【解决方案25】:
                              /*
                              * make a list of all distinct sets of coins of from the set of coins to
                              * sum up to the given target amount.
                              * Here the input set of coins is assumed yo be {1, 2, 4}, this set MUST
                              * have the coins sorted in ascending order.
                              * Outline of the algorithm:
                              * 
                              * Keep track of what the current coin is, say ccn; current number of coins
                              * in the partial solution, say k; current sum, say sum, obtained by adding
                              * ccn; sum sofar, say accsum:
                              *  1) Use ccn as long as it can be added without exceeding the target
                              *     a) if current sum equals target, add cc to solution coin set, increase
                              *     coin coin in the solution by 1, and print it and return
                              *     b) if current sum exceeds target, ccn can't be in the solution, so
                              *        return
                              *     c) if neither of the above, add current coin to partial solution,
                              *        increase k by 1 (number of coins in partial solution), and recuse
                              *  2) When current denomination can no longer be used, start using the
                              *     next higher denomination coins, just like in (1)
                              *  3) When all denominations have been used, we are done
                              */
                              
                              #include <iostream>
                              #include <cstdlib>
                              
                              using namespace std;
                              
                              // int num_calls = 0;
                              // int num_ways = 0;
                              
                              void print(const int coins[], int n);
                              
                              void combine_coins(
                                                 const int denoms[], // coins sorted in ascending order
                                                 int n,              // number of denominations
                                                 int target,         // target sum
                                                 int accsum,         // accumulated sum
                                                 int coins[],        // solution set, MUST equal
                                                                     // target / lowest denom coin
                                                 int k               // number of coins in coins[]
                                                )
                              {
                              
                                  int  ccn;   // current coin
                                  int  sum;   // current sum
                              
                                  // ++num_calls;
                              
                                  for (int i = 0; i < n; ++i) {
                                      /*
                                       * skip coins of lesser denomination: This is to be efficient
                                       * and also avoid generating duplicate sequences. What we need
                                       * is combinations and without this check we will generate
                                       * permutations.
                                       */
                                      if (k > 0 && denoms[i] < coins[k - 1])
                                          continue;   // skip coins of lesser denomination
                              
                                      ccn = denoms[i];
                              
                                      if ((sum = accsum + ccn) > target)
                                          return;     // no point trying higher denominations now
                              
                              
                                      if (sum == target) {
                                          // found yet another solution
                                          coins[k] = ccn;
                                          print(coins, k + 1);
                                          // ++num_ways;
                                          return;
                                      }
                              
                                      coins[k] = ccn;
                                      combine_coins(denoms, n, target, sum, coins, k + 1);
                                  }
                              }
                              
                              void print(const int coins[], int n)
                              {
                                  int s = 0;
                                  for (int i = 0; i < n; ++i) {
                                      cout << coins[i] << " ";
                                      s += coins[i];
                                  }
                                  cout << "\t = \t" << s << "\n";
                              
                              }
                              
                              int main(int argc, const char *argv[])
                              {
                              
                                  int denoms[] = {1, 2, 4};
                                  int dsize = sizeof(denoms) / sizeof(denoms[0]);
                                  int target;
                              
                                  if (argv[1])
                                      target = atoi(argv[1]);
                                  else
                                      target = 8;
                              
                                  int *coins = new int[target];
                              
                              
                                  combine_coins(denoms, dsize, target, 0, coins, 0);
                              
                                  // cout << "num calls = " << num_calls << ", num ways = " << num_ways << "\n";
                              
                                  return 0;
                              }
                              

                              【讨论】:

                                【解决方案26】:

                                这是一个 C# 函数:

                                    public static void change(int money, List<int> coins, List<int> combination)
                                    {
                                        if(money < 0 || coins.Count == 0) return;
                                        if (money == 0)
                                        {
                                            Console.WriteLine((String.Join("; ", combination)));
                                            return;
                                        }
                                
                                        List<int> copy = new List<int>(coins);
                                        copy.RemoveAt(0);
                                        change(money, copy, combination);
                                
                                        combination = new List<int>(combination) { coins[0] };
                                        change(money - coins[0], coins, new List<int>(combination));
                                    }
                                

                                像这样使用它:

                                change(100, new List<int>() {5, 10, 25}, new List<int>());
                                

                                打印出来:

                                25; 25; 25; 25
                                10; 10; 10; 10; 10; 25; 25
                                10; 10; 10; 10; 10; 10; 10; 10; 10; 10
                                5; 10; 10; 25; 25; 25
                                5; 10; 10; 10; 10; 10; 10; 10; 25
                                5; 5; 10; 10; 10; 10; 25; 25
                                5; 5; 10; 10; 10; 10; 10; 10; 10; 10; 10
                                5; 5; 5; 10; 25; 25; 25
                                5; 5; 5; 10; 10; 10; 10; 10; 10; 25
                                5; 5; 5; 5; 10; 10; 10; 25; 25
                                5; 5; 5; 5; 10; 10; 10; 10; 10; 10; 10; 10
                                5; 5; 5; 5; 5; 25; 25; 25
                                5; 5; 5; 5; 5; 10; 10; 10; 10; 10; 25
                                5; 5; 5; 5; 5; 5; 10; 10; 25; 25
                                5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 10; 10; 10
                                5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 10; 25; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 10; 10
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 25; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 10
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 25
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10
                                5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5
                                

                                【讨论】:

                                • 输出很漂亮
                                【解决方案27】:

                                下面是一个查找所有货币组合的python程序。这是一个具有 order(n) 时间的动态规划解决方案。 钱是1,5,10,25

                                我们从第 1 行钱遍历到第 25 行钱(4 行)。如果我们只考虑货币 1,则货币 1 行包含计数 计算组合的数量。行 money 5 通过计算行 money r 中的计数来生成每一列 相同的最终钱加上前 5 个计数在它自己的行中(当前位置减 5)。行钱 10 使用行钱 5, 其中包含 1,5 的计数并添加前 10 个计数(当前位置减 10)。行钱25用行 money 10,其中包含行 money 1、5、10 加上前 25 个计数的计数。

                                例如,numbers[1][12] = numbers[0][12] + numbers[1][7] (7 = 12-5),结果为 3 = 1 + 2;数字[3][12] = numbers[2][12] + numbers[3][9] (-13 = 12-25) 结果为 4 = 0 + 4,因为 -13 小于 0。

                                def cntMoney(num):
                                    mSz = len(money)
                                    numbers = [[0]*(1+num) for _ in range(mSz)]
                                    for mI in range(mSz): numbers[mI][0] = 1
                                    for mI,m in enumerate(money):
                                        for i in range(1,num+1):
                                            numbers[mI][i] = numbers[mI][i-m] if i >= m else 0
                                            if mI != 0: numbers[mI][i] += numbers[mI-1][i]
                                        print('m,numbers',m,numbers[mI])
                                    return numbers[mSz-1][num]
                                
                                money = [1,5,10,25]
                                    num = 12
                                    print('money,combinations',num,cntMoney(num))
                                
                                output:    
                                ('m,numbers', 1, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
                                ('m,numbers', 5, [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3])
                                ('m,numbers', 10, [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 4])
                                ('m,numbers', 25, [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 4])
                                ('money,combinations', 12, 4)
                                

                                【讨论】:

                                  【解决方案28】:

                                  Java 解决方案

                                  import java.util.Arrays;
                                  import java.util.Scanner;
                                  
                                  
                                  public class nCents {
                                  
                                  
                                  
                                  public static void main(String[] args) {
                                  
                                      Scanner input=new Scanner(System.in);
                                      int cents=input.nextInt();
                                      int num_ways [][] =new int [5][cents+1];
                                  
                                      //putting in zeroes to offset
                                      int getCents[]={0 , 0 , 5 , 10 , 25};
                                      Arrays.fill(num_ways[0], 0);
                                      Arrays.fill(num_ways[1], 1);
                                  
                                      int current_cent=0;
                                      for(int i=2;i<num_ways.length;i++){
                                  
                                          current_cent=getCents[i];
                                  
                                          for(int j=1;j<num_ways[0].length;j++){
                                              if(j-current_cent>=0){
                                                  if(j-current_cent==0){
                                                      num_ways[i][j]=num_ways[i-1][j]+1;
                                                  }else{
                                                      num_ways[i][j]=num_ways[i][j-current_cent]+num_ways[i-1][j];
                                                  }
                                              }else{
                                                  num_ways[i][j]=num_ways[i-1][j];
                                              }
                                  
                                  
                                          }
                                  
                                  
                                      }
                                  
                                  
                                  
                                      System.out.println(num_ways[num_ways.length-1][num_ways[0].length-1]);
                                  
                                  }
                                  

                                  }

                                  【讨论】:

                                    【解决方案29】:

                                    下面的 java 解决方案也将打印不同的组合。容易明白。想法是

                                    对于总和 5

                                    解决办法是

                                        5 - 5(i) times 1 = 0
                                            if(sum = 0)
                                               print i times 1
                                        5 - 4(i) times 1 = 1
                                        5 - 3 times 1 = 2
                                            2 -  1(j) times 2 = 0
                                               if(sum = 0)
                                                  print i times 1 and j times 2
                                        and so on......
                                    

                                    如果每个循环中的剩余总和小于面额即 如果剩余总和 1 小于 2,则直接中断循环

                                    完整代码如下

                                    如有错误请指正

                                    public class CoinCombinbationSimple {
                                    public static void main(String[] args) {
                                        int sum = 100000;
                                        printCombination(sum);
                                    }
                                    
                                    static void printCombination(int sum) {
                                        for (int i = sum; i >= 0; i--) {
                                            int sumCopy1 = sum - i * 1;
                                            if (sumCopy1 == 0) {
                                                System.out.println(i + " 1 coins");
                                            }
                                            for (int j = sumCopy1 / 2; j >= 0; j--) {
                                                int sumCopy2 = sumCopy1;
                                                if (sumCopy2 < 2) {
                                                    break;
                                                }
                                                sumCopy2 = sumCopy1 - 2 * j;
                                                if (sumCopy2 == 0) {
                                                    System.out.println(i + " 1 coins " + j + " 2 coins ");
                                                }
                                                for (int k = sumCopy2 / 5; k >= 0; k--) {
                                                    int sumCopy3 = sumCopy2;
                                                    if (sumCopy2 < 5) {
                                                        break;
                                                    }
                                                    sumCopy3 = sumCopy2 - 5 * k;
                                                    if (sumCopy3 == 0) {
                                                        System.out.println(i + " 1 coins " + j + " 2 coins "
                                                                + k + " 5 coins");
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    

                                    }

                                    【讨论】:

                                      【解决方案30】:

                                      这是一个基于 python 的解决方案,它使用递归和记忆,导致 O(mxn) 的复杂度

                                          def get_combinations_dynamic(self, amount, coins, memo):
                                          end_index = len(coins) - 1
                                          memo_key = str(amount)+'->'+str(coins)
                                          if memo_key in memo:
                                              return memo[memo_key]
                                          remaining_amount = amount
                                          if amount < 0:
                                              return []
                                          if amount == 0:
                                              return [[]]
                                          combinations = []
                                          if len(coins) <= 1:
                                              if amount % coins[0] == 0:
                                                  combination = []
                                                  for i in range(amount // coins[0]):
                                                      combination.append(coins[0])
                                                  list.sort(combination)
                                                  if combination not in combinations:
                                                      combinations.append(combination)
                                          else:
                                              k = 0
                                              while remaining_amount >= 0:
                                                  sub_combinations = self.get_combinations_dynamic(remaining_amount, coins[:end_index], memo)
                                                  for combination in sub_combinations:
                                                      temp = combination[:]
                                                      for i in range(k):
                                                          temp.append(coins[end_index])
                                                      list.sort(temp)
                                                      if temp not in combinations:
                                                          combinations.append(temp)
                                                  k += 1
                                                  remaining_amount -= coins[end_index]
                                          memo[memo_key] = combinations
                                          return combinations
                                      

                                      【讨论】:

                                      • 好吧,我怀疑上面有多项式运行时间。不确定我们是否可以有多项式运行时间。但我观察到的是,在许多情况下,上述运行速度比非记忆版本快。我会继续研究原因
                                      猜你喜欢
                                      • 2015-12-22
                                      • 1970-01-01
                                      • 2021-07-30
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 1970-01-01
                                      • 2016-01-25
                                      • 1970-01-01
                                      • 2021-12-25
                                      相关资源
                                      最近更新 更多