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

时间:2010-11-22 09:10:31

标签: java algorithm combinations

我正在尝试实施硬币问题,问题规范是这样的

创建一个函数来计算可用于给定数量的所有可能的硬币组合。

All possible combinations for given amount=15, coin types=1 6 7 
1) 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2) 1,1,1,1,1,1,1,1,1,6,
3) 1,1,1,1,1,1,1,1,7,
4) 1,1,1,6,6,
5) 1,1,6,7,
6) 1,7,7,

函数原型:

int findCombinationsCount(int amount, int coins[])

假设硬币阵列已排序。对于上面的例子,这个函数应该返回6.

有人指导我如何实现这个??

16 个答案:

答案 0 :(得分:34)

使用递归。

int findCombinationsCount(int amount, int coins[]) {
    return findCombinationsCount(amount, coins, 0);
}

int findCombinationsCount(int amount, int coins[], int checkFromIndex) {
    if (amount == 0)
        return 1;
    else if (amount < 0 || coins.length == checkFromIndex)
        return 0;
    else {
        int withFirstCoin = findCombinationsCount(amount-coins[checkFromIndex], coins, checkFromIndex);
        int withoutFirstCoin = findCombinationsCount(amount, coins, checkFromIndex+1);
        return withFirstCoin + withoutFirstCoin;
    }
}

您应该检查此实现。我这里没有Java IDE,而且我有点生疏,所以可能会有一些错误。

答案 1 :(得分:13)

您可以使用生成函数方法来提供使用复数的快速算法。

给定硬币值c1,c2,..,ck,得到求和的方法数n,你需要的是x ^ n的系数

(1 + x^c1 + x^(2c1) + x^(3c1) + ...)(1+x^c2 + x^(2c2) + x^(3c2) + ...)....(1+x^ck + x^(2ck) + x^(3ck) + ...)

中找到x ^ n的系数相同
1/(1-x^c1) * 1/(1-x^c2) * ... * (1-x^ck)

现在使用复数,x ^ a - 1 =(x-w1)(x-w2)...(x-wa)其中w1,w2等是统一的复杂根。

所以

1/(1-x^c1) * 1/(1-x^c2) * ... * (1-x^ck)

可以写成

1/(x-a1)(x-a2)....(x-am)

可以使用部分分数重写

A1/(x-a1) + A2/(x-a2) + ... + Am/(x-am)

可以很容易地找到x ^ n的系数:

A1/(a1)^(n+1) + A2/(a2)^(n+1) + ...+ Am/(am)^(n+1).

计算机程序应该能够轻松找到Ai和ai(可能是复数)。当然,这可能涉及浮点计算。

对于大n,这可能比枚举所有可能的组合更快。

希望有所帮助。

答案 2 :(得分:11)

虽然递归可以起作用,但通常是在一些关于算法和算法的大学课程中实施的任务。数据结构,我相信“动态编程”实现更有效。

public static int findCombinationsCount(int sum, int vals[]) {
        if (sum < 0) {
            return 0;
        }
        if (vals == null || vals.length == 0) {
            return 0;
        }

        int dp[] = new int[sum + 1];
        dp[0] = 1;
        for (int i = 0; i < vals.length; ++i) {
            for (int j = vals[i]; j <= sum; ++j) {
                dp[j] += dp[j - vals[i]];
            }
        }
        return dp[sum];
    }

答案 3 :(得分:6)

递归非常简单:

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

   if(money <= 0 || coins.isEmpty) 0
   else reduce(money, coins, 0)
}

这是SCALA中的示例

答案 4 :(得分:3)

Aryabhatta’s answer 计算用固定硬币进行更改的方法数量 面额非常可爱,但实施起来也不切实际 描述。我们将使用模块化而不是使用复数 算术,类似于数论变换如何取代a 用于乘以整数多项式的傅里叶变换。

D成为硬币面额中最不常见的倍数。通过 Dirichlet关于算术进展的定理存在无限 许多素数pD除以p - 1。 (运气好的话, 它们甚至会以我们可以找到它们的方式分发 有效率。)我们将计算以p为模的方式的数量 满足这个条件。通过某种方式获得原油界限(例如, n + k - 1选择k - 1,其中n为总数,k为数字 (几种),用几种不同的方法重复这个过程 产品超过该范围的素数,并应用中文 余数定理,我们可以恢复确切的数字。

在我们找到素数之前,为整数1 + k*D测试候选人k > 0 p。设g为原始根模p(生成候选者) 随机并应用标准测试)。对于每个面额d,请表达 多项式x**d - 1 modulo p作为因子的乘积:

x**d - 1 = product from i=0 to d-1 of (x - g**((p-1)*i/d)) [modulo p].

请注意dDp-1,所以指数确实是一个 整数。

m为面额的总和。收集所有常量 g**((p-1)*i/d)a(0), ..., a(m-1)。下一步是找到一个 部分分数分解A(0), ..., A(m-1)使

sign / product from j=0 to m-1 of (a(j) - x) =
    sum from j=0 to m-1 of A(j)/(a(j) - x) [modulo p],

如果有多个面额且sign1 -1如果有一定数量的面额。派生一个系统 A(j)的线性方程通过评估给定的两边 对于x的不同值的等式,然后用高斯求解它 淘汰。如果有重复,生活会变得复杂;它可能最容易选择另一个素数。

鉴于此设置,我们可以计算方式的数量(模p 当然)将变更金额n作为

sum from j=0 to m-1 of A(j) * (1/a(j))**(n+1).

答案 5 :(得分:2)

package algorithms;

import java.util.Random;

/**`enter code here`
 * Owner : Ghodrat Naderi
 * E-Mail: Naderi.ghodrat@gmail.com
 * Date  : 10/12/12
 * Time  : 4:50 PM
 * IDE   : IntelliJ IDEA 11
 */
public class CoinProblem
 {
  public static void main(String[] args)
   {
    int[] coins = {1, 3, 5, 10, 20, 50, 100, 200, 500};

    int amount = new Random().nextInt(10000);
    int coinsCount = 0;
    System.out.println("amount = " + amount);
    int[] numberOfCoins = findNumberOfCoins(coins, amount);
    for (int i = 0; i < numberOfCoins.length; i++)
     {
      if (numberOfCoins[i] > 0)
       {
        System.out.println("coins= " + coins[i] + " Count=" + numberOfCoins[i] + "\n");
        coinsCount += numberOfCoins[i];
       }

     }
    System.out.println("numberOfCoins = " + coinsCount);
   }

  private static int[] findNumberOfCoins(int[] coins, int amount)
   {
    int c = coins.length;
    int[] numberOfCoins = new int[coins.length];
    while (amount > 0)
     {
      c--;
      if (amount >= coins[c])
       {
        int quotient = amount / coins[c];
        amount = amount - coins[c] * quotient;
        numberOfCoins[c] = quotient;
       }

     }
    return numberOfCoins;
   }
 }

答案 6 :(得分:1)

递归解决方案可能是正确答案:

int findCombinationsCount(int amount, int coins[])
{
    // I am assuming amount >= 0, coins.length > 0 and all elements of coins > 0.
    if (coins.length == 1)
    {
        return amount % coins[0] == 0 ? 1 : 0;
    }
    else
    {
        int total = 0;
        int[] subCoins = arrayOfCoinsExceptTheFirstOne(coins);
        for (int i = 0 ; i * coins[0] <= amount ; ++i)
        {
            total += findCombinationsCount(amount - i * coins[0], subCoins);
        }
        return total;
    }
}

警告:我没有测试过甚至编译过上述内容。

答案 7 :(得分:1)

提到的递归解决方案可行,但如果您添加更多硬币面额和/或显着提高目标值,它们将会非常缓慢。

加速它需要的是实现动态编程解决方案。看看knapsack problem。你可以调整那里提到的DP解决方案来解决你的问题,方法是保持计算总数的方式,而不是所需的最小硬币数。

答案 8 :(得分:1)

@Jordi提供的解决方案很不错但运行速度非常慢。您可以尝试输入600到该解决方案,看看它有多慢。

我的想法是使用自下而上的动态编程。

请注意,通常情况下,money = m和硬币{a,b,c}的可能组合等于

的组合
  • m-c和硬币{a,b,c}(带硬币c)
  • m和币的组合{a,b}(没有硬币c)。

如果没有可用的硬币或可用的硬币无法支付所需的金额,则应相应地填写0。如果金额为0,则应填写1。

public static void main(String[] args){
    int[] coins = new int[]{1,2,3,4,5};
    int money = 600;
    int[][] recorder = new int[money+1][coins.length];
    for(int k=0;k<coins.length;k++){
        recorder[0][k] = 1;
    }
    for(int i=1;i<=money;i++){
        //System.out.println("working on money="+i);
        int with = 0;
        int without = 0;

        for(int coin_index=0;coin_index<coins.length;coin_index++){
            //System.out.println("working on coin until "+coins[coin_index]);
            if(i-coins[coin_index]<0){
                with = 0;
            }else{
                with = recorder[i-coins[coin_index]][coin_index];
            }
            //System.out.println("with="+with);
            if(coin_index-1<0){
                without = 0;
            }else{
                without = recorder[i][coin_index-1];
            }
            //System.out.println("without="+without);
            //System.out.println("result="+(without+with));
            recorder[i][coin_index] =  with+without;
        }
    }
    System.out.print(recorder[money][coins.length-1]);

}

答案 9 :(得分:1)

此代码基于JeremyP提供的解决方案,该解决方案完美无缺,我只是通过使用动态编程来增强它以优化性能。我无法对JeremyP帖子发表评论,因为我没有足够的声誉:)

public static long makeChange(int[] coins, int money) {
    Long[][] resultMap = new Long[coins.length][money+1];
    return getChange(coins,money,0,resultMap);
}

public static long getChange(int[] coins, int money, int index,Long[][] resultMap) {
    if (index == coins.length -1) // if we are at the end      
        return money%coins[index]==0? 1:0;
    else{
        //System.out.printf("Checking index %d and money %d ",index,money);
        Long storedResult =resultMap[index][money];
        if(storedResult != null)
            return storedResult;
        long total=0;
        for(int coff=0; coff * coins[index] <=money; coff ++){

             total += getChange(coins, money - coff*coins[index],index +1,resultMap);
        }
        resultMap[index][money] = total;
        return total;

    }
}

答案 10 :(得分:0)

第一个想法:

int combinations = 0;
for (int i = 0; i * 7 <=15; i++) {
    for (int j = 0; j * 6 + i * 7 <= 15; j++) {
      combinations++;
    }
}

(在这种情况下,'&lt; ='是多余的,但如果您决定更改参数,则需要更通用的解决方案)

答案 11 :(得分:0)

再次使用递归测试解决方案,尽管可能不是最优雅的代码。 (注意它返回使用的每枚硬币的数量,而不是重复实际的硬币数量n次)。

public class CoinPerm {


@Test
public void QuickTest() throws Exception
{
    int ammount = 15;
    int coins[] = {1,6,7};

    ArrayList<solution> solutionList = SolvePerms(ammount, coins);

    for (solution sol : solutionList)
    {
        System.out.println(sol);
    }

    assertTrue("Wrong number of solutions " + solutionList.size(),solutionList.size()  == 6);
}



public ArrayList<solution>  SolvePerms(int ammount, int coins[]) throws Exception
{
    ArrayList<solution> solutionList = new ArrayList<solution>();
    ArrayList<Integer> emptyList = new ArrayList<Integer>();
    solution CurrentSolution = new solution(emptyList);
    GetPerms(ammount, coins, CurrentSolution, solutionList);

    return solutionList;
}


private void GetPerms(int ammount, int coins[], solution CurrentSolution,   ArrayList<solution> mSolutions) throws Exception
{
    int currentCoin = coins[0];

    if (currentCoin <= 0)
    {
        throw new Exception("Cant cope with negative or zero ammounts");
    }

    if (coins.length == 1)
    {
        if (ammount % currentCoin == 0)
        {
            CurrentSolution.add(ammount/currentCoin);
            mSolutions.add(CurrentSolution);
        }
        return;
    }

    // work out list with one less coin.
    int coinsDepth = coins.length;
    int reducedCoins[] = new int[(coinsDepth -1 )];
    for (int j = 0; j < coinsDepth - 1;j++)
    {
        reducedCoins[j] = coins[j+1];
    }


    // integer rounding okay;
    int numberOfPerms = ammount / currentCoin;

    for (int j = 0; j <= numberOfPerms; j++)
    {
        solution newSolution =  CurrentSolution.clone();
        newSolution.add(j);
        GetPerms(ammount - j * currentCoin,reducedCoins, newSolution, mSolutions ); 
    }
}


private class solution 
{
    ArrayList<Integer> mNumberOfCoins;

    solution(ArrayList<Integer> anumberOfCoins)
    {
        mNumberOfCoins = anumberOfCoins;
    }

    @Override
    public String toString() {
        if (mNumberOfCoins != null && mNumberOfCoins.size() > 0)
        {
            String retval = mNumberOfCoins.get(0).toString();
            for (int i = 1; i< mNumberOfCoins.size();i++)
            {
                retval += ","+mNumberOfCoins.get(i).toString();
            }
            return retval;
        }
        else
        {
            return "";
        }
    }

    @Override
    protected solution clone() 
    {
        return new solution((ArrayList<Integer>) mNumberOfCoins.clone());
    }

    public void add(int i) {
        mNumberOfCoins.add(i);
    }
}

}

答案 12 :(得分:0)

下面是memoization java解决方案的递归。对于低于1,我们有1,2,3,5作为硬币,200作为目标金额。

countCombinations(200,new int[]{5,2,3,1} , 0, 0,new Integer[6][200+5]);

static int countCombinations(Integer targetAmount, int[] V,int currentAmount, int coin, Integer[][] memory){

    //Comment below if block if you want to see the perf difference
    if(memory[coin][currentAmount] != null){
        return memory[coin][currentAmount];
    }

    if(currentAmount > targetAmount){
        memory[coin][currentAmount] = 0;
        return 0;
    }
    if(currentAmount == targetAmount){
        return 1;
    }      
    int count = 0;
    for(int selectedCoin : V){
        if(selectedCoin >= coin){                
            count += countCombinations(targetAmount, V, currentAmount+selectedCoin, selectedCoin,memory);
        }
    }        
    memory[coin][currentAmount] = count;        
    return count;
}

答案 13 :(得分:0)

#include<iostream>
using namespace std;

int solns = 0;

void countComb(int* arr, int low, int high, int Val)
{
    bool b = false;
    for (size_t i = low; i <= high; i++)
    {
        if (Val - arr[i] == 0)
        {
            solns++;
            break;
        }
        else if (Val - arr[i] > 0)
            countComb(arr, i, high, Val - arr[i]);
        
    }
}

int main()
{
    int coins[] = { 1,2,5 };
    int value = 7;
    int arrSize = sizeof(coins) / sizeof(int);
    countComb(coins,0, arrSize,value);
    cout << solns << endl;
    return 0;
}

答案 14 :(得分:-1)

public static void main(String[] args) {

    int b,c,total = 15;
    int combos =1;
        for(int d=0;d<total/7;d++)
           {
             b = total - d * 7;
            for (int n = 0; n <= b /6; n++)
        {
                    combos++;

        }

        }

      System.out.print("TOTAL COMBINATIONS  = "+combos);
}

答案 15 :(得分:-8)

硬币(1,5,10,25,50)的相同问题具有以下解决方案之一。 解决方案应满足以下等式: 1 * a + 5 * b + 10 * c + 25 * d + 50 * e ==美分

public static void countWaysToProduceGivenAmountOfMoney(int cents){

    for(int a = 0;a<=cents;a++){
        for(int b = 0;b<=cents/5;b++){
            for(int c = 0;c<=cents/10;c++){
                for(int d = 0;d<=cents/25;d++){
                    for(int e = 0;e<=cents/50;e++){
                        if(1*a + 5*b + 10*c + 25*d + 50*e == cents){
                            System.out.println("1 cents :"+a+", 5 cents:"+b+", 10 cents:"+c);
                        }
                    }
                }
            }
        }
    }
}

可针对任何常规解决方案进行修改。