web-dev-qa-db-ja.com

コイン問題の可能な組み合わせを数える方法

私はコインの問題を実装しようとしています、問題の仕様はこのようなものです

特定の金額に使用できるコインのすべての可能な組み合わせをカウントする関数を作成します。

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を返します。

誰がこれを実装する方法を教えてくれますか?

24
Preetam Purbia

生成関数メソッドを使用して、複素数を使用する高速アルゴリズムを提供できます。

コインの値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/(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が大きい場合、これはおそらくすべての可能な組み合わせを列挙するよりも高速です。

お役に立てば幸いです。

13
Aryabhatta

再帰を使用します。

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ここにあり、少し錆びているので、エラーがあるかもしれません。

35
Jordi

再帰は機能し、多くの場合、アルゴリズムとデータ構造に関するいくつかの大学レベルのコースで実装するための割り当てですが、「動的プログラミング」実装はより効率的であると思います。

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];
    }
11
Domenic D.

再帰を使うと非常に簡単です:

 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の例です

6
Vlad

Aryabhattaの答え 固定金種のコインで変更を行う方法の数を数えることは非常にかわいいですが、説明されているように実装することも非現実的です。複素数を使用するのではなく、整数多項式を乗算するフーリエ変換を数論変換が置き換える方法と同様に、モジュラー演算を使用します。

Dをコイン単位の最小公倍数とします。算術級数に関するディリクレの定理によれば、pが_p - 1_を分割するような無数の素数Dが存在します。 (運が良ければ、それらは効率的に見つけることができるような方法で配布されます。)この条件を満たすいくつかのpを法とする方法の数を計算します。粗雑な境界を何らかの方法で取得する(たとえば、_n + k - 1_を選択して_k - 1_を選択します。ここで、nは合計で、kは宗派の数です)。中国の剰余定理、正確な数を回復できます。

素数pが見つかるまで、整数_1 + k*D_の候補_k > 0_をテストします。 gpを法とするプリミティブルートとします(ランダムに候補を生成し、標準テストを適用します)。各変数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].
_

dDを除算して_p-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],
_

ここで、signは、宗派の数が偶数の場合は_1_、宗派の数が奇数の場合は_-1_です。 xの異なる値に対して指定された方程式の両側を評価することにより、A(j)の線形方程式を導き出し、ガウス消去法で解きます。重複があると人生は複雑になります。別の素数を選択するのがおそらく最も簡単です。

この設定を前提として、次のようにpに相当する変更を行う方法の数(もちろん、モジュロn)を計算できます。

_sum from j=0 to m-1 of A(j) * (1/a(j))**(n+1).
_
3
David Eisenstat
package algorithms;

import Java.util.Random;

/**`enter code here`
 * Owner : Ghodrat Naderi
 * E-Mail: [email protected]
 * 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;
   }
 }
2
Ghodrat Naderi

前述の再帰的なソリューションは機能しますが、コインの種類を追加したり、ターゲット値を大幅に増やしたりすると、恐ろしく遅くなります。

高速化するために必要なのは、動的プログラミングソリューションを実装することです。 knapsack problem をご覧ください。必要なコインの最小数ではなく、合計に到達できる方法の数を数えることで、そこに記載されているDPソリューションを調整して問題を解決できます。

1
marcog

再帰的な解決策はここで正しい答えかもしれません:

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;
    }
}

警告:上記をテストしたりコンパイルしたりしていません。

1
JeremyP

@Jordiが提供するソリューションは素晴らしいですが、実行は非常に遅くなります。そのソリューションへの入力600を試してみて、それがどれほど遅いかを見ることができます。

私の考えは、ボトムアップの動的プログラミングを使用することです。

一般的に、money = mとcoins {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]);

}
1
Liu Dake

このコードは完璧に機能している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;

    }
}
1
Mahmoud Aziz

繰り返しますが、おそらく最もエレガントなコードではありませんが、テスト済みのソリューションを使用します。 (実際のコインの金額を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);
    }
}

}

0
aronp

以下は、メモ化を伴う再帰ですJavaソリューション。

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;
}
0
fatih tekin

最初のアイデア:

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

(この場合、「<=」は不要ですが、パラメータを変更する場合は、より一般的なソリューションに必要です)

0
G B