Comment trouver toutes les combinaisons de pièces lorsqu'on leur donne une valeur en dollars


114

J'ai trouvé un morceau de code que j'écrivais pour la préparation de l'entrevue il y a quelques mois.

D'après le commentaire que j'ai eu, il essayait de résoudre ce problème:

Étant donné une certaine valeur en dollars en cents (par exemple 200 = 2 dollars, 1000 = 10 dollars), trouvez toutes les combinaisons de pièces qui composent la valeur en dollars. Seuls les centimes (1 ¢), les nickels (5 ¢), les dix sous (10 ¢) et les quarts (25 ¢) sont autorisés.

Par exemple, si 100 a été donné, la réponse devrait être:

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

Je pense que cela peut être résolu de manière itérative et récursive. Ma solution récursive est assez boguée, et je me demandais comment d'autres personnes pourraient résoudre ce problème. La partie difficile de ce problème était de le rendre aussi efficace que possible.


6
@akappa: centime = 1 centime; nickel = 5 cents; dix cents = 10 cents; quarter = 25 cents :)
codingbear

@John T: code golf? Je n'ai jamais entendu parler de ce terme! Quoi qu'il en soit, j'espère voir des réponses intéressantes, car la communauté SO peut résoudre n'importe quel problème
codingbear

J'essaierai aussi de poster ma réponse une fois rentrée à la maison ... toujours au travail et je ne devrais pas passer trop de temps sur SO.
codingbear

1
@blee code golf fait référence à la résolution d'un problème avec le moins de caractères possible, avec le langage de programmation de votre choix. En voici quelques-unes qui ont été réalisées sur ce site: stackoverflow.com/search?q=code+golf
John T

Réponses:


54

J'ai examiné cela une fois il y a longtemps, et vous pouvez lire mon petit article à ce sujet . Voici la source Mathematica .

En utilisant des fonctions de génération, vous pouvez obtenir une solution à temps constant de forme fermée au problème. Mathématiques concrètes de Graham, Knuth et Patashnik est le livre pour cela, et contient une discussion assez approfondie du problème. Essentiellement, vous définissez un polynôme où le n ème coefficient est le nombre de façons de faire un changement pour n dollars.

Les pages 4 à 5 de l'écriture montrent comment vous pouvez utiliser Mathematica (ou tout autre système d'algèbre informatique pratique) pour calculer la réponse pour 10 ^ 10 ^ 6 dollars en quelques secondes en trois lignes de code.

(Et c'était il y a assez longtemps que ça fait quelques secondes sur un Pentium 75Mhz ...)


16
Bonne réponse, mais petits problèmes: notez que (1) Ceci donne le nombre de voies, alors que pour une raison quelconque, la question demande l'ensemble réel de toutes les voies. Bien sûr, il ne peut y avoir aucun moyen de trouver l'ensemble en temps polynomial, puisque la sortie elle-même a de nombreuses entrées superpolynomiales (2) On peut se demander si une fonction génératrice est une "forme fermée" (voir le merveilleux livre d'Herbert Wilf Generatingfunctionology : math. upenn.edu/~wilf/DownldGF.html ) et si vous voulez dire une expression comme (1 + √5) ^ n, il faut Ω (log n) temps pour calculer, pas un temps constant.
ShreevatsaR

Introduction douce à la programmation dynamique. En outre, j'encourage toute personne ayant un problème de séquence à lire la fonctionnalité de génération .
Colonel Panic

Merci beaucoup Andrew ... cette explication m'a beaucoup aidé ... Poster la fonction scala ci-dessous ... si quelqu'un en a besoin
jayaram S

1
Je crois que la question du début a besoin d'une légère correction car elle demande "... en utilisant des pièces de 1, 10, 25, 50 et 100 cents?" Mais alors l'écriture définit l'ensemble acomme le domaine de fmais a = {1,5,10,25,50,100}. Il devrait y avoir un 5 dans la liste des pièces de cent. Sinon, la rédaction était fantastique, merci!
rbrtl

@rbrtl Wow, vous avez raison, merci de l'avoir remarqué! Je vais le mettre à jour…
andrewdotn

42

Remarque : cela montre uniquement le nombre de façons.

Fonction 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)

1
Y a-t-il vraiment une façon de changer 0? Je suppose qu'il n'y a aucun moyen de faire cela.
Luc

2
Il provient du nombre de solutions polynomiales n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. Donc pour money=0et coins=List(1,2,5,10)le nombre de combinaisons (n1, n2, n3, n4)est 1 et la solution est (0, 0, 0, 0).
Kyr

3
Je ne peux pas comprendre pourquoi cette mise en œuvre fonctionne. Quelqu'un peut-il m'expliquer l'algorithme derrière?
Adrien Lemaire

3
C'est certainement la réponse exacte au problème 3 de l'exercice 1 du cours coursera scala.
Justin Standard le

Je crois que, si money == 0mais coins.isEmpty, cela ne devrait pas compter comme un sol'n. Par conséquent, l'algo peut être mieux servi si la coins.isEmpty || money < 0condition est satisfaite en premier.
juanchito

26

Je serais favorable à une solution récursive. Vous avez une liste de dénominations, si la plus petite peut diviser uniformément le montant restant en devise, cela devrait fonctionner correctement.

Fondamentalement, vous passez de la plus grande à la plus petite dénomination.
Récursivement,

  1. Vous avez un total actuel à remplir et une plus grande dénomination (avec plus de 1 à gauche). S'il ne reste qu'une seule dénomination, il n'y a qu'une seule façon de remplir le total. Vous pouvez utiliser de 0 à k copies de votre dénomination actuelle de telle sorte que k * cur dénomination <= total.
  2. Pour 0 à k, appelez la fonction avec le total modifié et la nouvelle plus grande dénomination.
  3. Additionnez les résultats de 0 à k. C'est de combien de façons vous pouvez remplir votre total à partir de la dénomination actuelle vers le bas. Renvoyez ce numéro.

Voici ma version python de votre problème déclaré, pour 200 cents. J'obtiens 1463 manières. Cette version imprime toutes les combinaisons et le total du comptage final.

#!/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)

Je ne l'ai pas exécuté, mais en passant par votre logique, cela a du sens :)
codingbear

Vous pouvez remplacer les deux dernières lignes de la fonction par "return sum (count_combs (...) for ...)" - de cette façon la liste ne se matérialise pas du tout. :)
Nick Johnson

Merci pour le conseil. Je suis toujours intéressé par les moyens de resserrer le code.
leif

2
Comme discuté dans une autre question , ce code donnera une sortie incorrecte si la liste de denominationsn'a pas 1comme dernière valeur. Vous pouvez ajouter une petite quantité de code au ifbloc le plus interne pour le corriger (comme je le décris dans ma réponse à l'autre question).
Blckknght

12

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

Merci! Ceci est un bon début. Mais, je pense que cela échoue lorsque "money" commence à être 0 :).
aqn

10

Voici un code C ++ absolument simple pour résoudre le problème qui demandait que toutes les combinaisons soient affichées.

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

Mais je suis assez intrigué par le sous-problème de calculer simplement le nombre de combinaisons. Je soupçonne qu'il existe une équation de forme fermée pour cela.


9
C'est sûrement C, pas C ++.
nikhil le

1
@George Phillips pouvez-vous expliquer?
Essai

Je pense que c'est assez simple. Fondamentalement, l'idée est d'itérer tous les quartiers (en utilisant 0,1,2 .. max), puis de parcourir tous les dix sous en fonction des quartiers utilisés, etc.
Peter Lee

4
L'inconvénient de cette solution est: s'il y a des pièces de 50 cents, 100 cents, 500 cents, nous devons utiliser des boucles à 6 niveaux ...
Peter Lee

3
C'est assez mauvais, si vous avez une dénomination dynamique ou si vous souhaitez ajouter une autre dénomination, cela ne fonctionnera pas.
shinzou

7

Le problème secondaire est un problème typique de programmation dynamique.

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

Vos solutions dynamiques nécessitent que k soit la longueur de C moins 1. un peu déroutant. Vous pouvez le changer facilement pour prendre en charge la longueur réelle de C.
Idan

7

Le code utilise Java pour résoudre ce problème et cela fonctionne également ... Cette méthode n'est peut-être pas une bonne idée à cause du trop grand nombre de boucles, mais c'est vraiment une méthode simple.

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

7

C'est une question très ancienne, mais j'ai trouvé une solution récursive en java qui semblait plus petite que toutes les autres, alors voilà -

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

Améliorations:

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

6

Soit C (i, J) l'ensemble des combinaisons de fabrication de i cents en utilisant les valeurs de l'ensemble J.

Vous pouvez définir C comme cela:

entrez la description de l'image ici

(first (J) prend de manière déterministe un élément d'un ensemble)

Cela s'avère une fonction assez récursive ... et raisonnablement efficace si vous utilisez la mémorisation;)


Ouais, ceci ("programmation dynamique", dans un sens) va être la solution optimale.
ShreevatsaR

vous avez raison: prenez J comme une liste et non comme un ensemble: alors first (J) vous apporte le premier élément et J \ first (J) vous donne le reste de la liste.
akappa le

quelle forme de maths s'agit-il?
Muhammad Umer

5

semi-hack pour contourner le problème de combinaison unique - force l'ordre décroissant:

$ denoms = [1,5,10,25]
def all_combs (somme, dernier) 
  renvoie 1 si somme == 0
  retourne $ denoms.select {| d | d & le somme && d & le dernier} .inject (0) {| total, dénom |
           total + all_combs (somme-dénom, dénom)}
fin

Cela fonctionnera lentement car il ne sera pas mémorisé, mais vous avez l'idée.


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

3

C'est ma réponse en Python. Il n'utilise pas la récursivité:

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)

Exemple de sortie

    ...
    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

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

2

Les deux: parcourir toutes les dénominations de haut en bas, prendre une dénomination, soustraire du total demandé, puis récurer sur le reste (contraindre les dénominations disponibles à être égales ou inférieures à la valeur d'itération actuelle.)


2

Si le système monétaire le permet, un simple algorithme gourmand qui prend autant de pièces que possible, en commençant par la devise de valeur la plus élevée.

Sinon, une programmation dynamique est nécessaire pour trouver rapidement une solution optimale puisque ce problème est essentiellement le problème du sac à dos .

Par exemple, si un système monétaire a les pièces {13, 8, 1}:, la solution gourmande ferait le changement pour 24 comme {13, 8, 1, 1, 1}, mais la vraie solution optimale est{8, 8, 8}

Edit: Je pensais que nous faisions des changements de manière optimale, sans énumérer toutes les façons de faire du changement pour un dollar. Mon récent entretien a demandé comment faire des changements, alors j'ai pris de l'avance avant de finir de lire la question.


le problème n'est pas nécessairement pour un dollar - il pourrait être 2 ou 23, donc votre solution est toujours la seule correcte.
Neil G

2

Je sais que c'est une question très ancienne. Je cherchais la bonne réponse et je n'ai rien trouvé de simple et satisfaisant. Cela m'a pris du temps mais j'ai pu noter quelque chose.

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

Ceci est une solution javascript et utilise la récursivité.


Cette solution ne trouve qu'une seule dénomination. La question était de trouver «toutes» les dénominations.
heinob

2

Dans le langage de programmation Scala, je le ferais comme ceci:

 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)

       }

  }

2

Il s'agit d'un simple algorithme récursif qui prend une facture, puis prend une facture plus petite de manière récursive jusqu'à ce qu'elle atteigne la somme, puis prend une autre facture de la même dénomination et se répète. Voir l'exemple de sortie ci-dessous pour l'illustration.

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, "");

Imprime les éléments suivants:

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

1

Duh, je me sens stupide en ce moment. Ci - dessous , il y a une solution trop compliquée, que je conserverai parce qu'il est une solution, après tout. Une solution simple serait la suivante:

// 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"

Voici l'autre solution. Cette solution est basée sur le constat que chaque pièce est un multiple des autres, afin qu'elles puissent être représentées en fonction d'elles.

// 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"
}

Donc, pour 37 pièces, par exemple:

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

1

Cette entrée de blog résout ce problème de sac à dos pour les personnages d'une bande dessinée XKCD . Une simple modification du itemsdict et de la exactcostvaleur donnera également toutes les solutions à votre problème.

Si le problème consistait à trouver le changement qui utilisait le moins de coût, alors un algorithme naïf avide qui utilisait autant de pièces de la plus grande valeur pourrait bien échouer pour certaines combinaisons de pièces et de montant cible. Par exemple, s'il y a des pièces avec les valeurs 1, 3 et 4; et le montant cible est de 6, alors l'algorithme gourmand pourrait suggérer trois pièces de valeur 4, 1 et 1 lorsqu'il est facile de voir que vous pouvez utiliser deux pièces chacune de valeur 3.

  • Paddy.

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

1

J'ai trouvé ce morceau de code dans le livre "Python For Data Analysis" par O'reily. Il utilise une implémentation paresseuse et une comparaison int et je suppose qu'il peut être modifié pour d'autres dénominations en utilisant des décimales. Faites moi savoir comment ça marche pour vous!

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


1

C'est l'amélioration de la réponse de Zihan. Le grand nombre de boucles inutiles survient lorsque la valeur nominale n'est que de 1 cent.

C'est intuitif et non récursif.

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

Vous ne pouvez pas généraliser cette solution, donc par exemple un nouvel élément apparaît dans ce cas, vous devez en ajouter un autre pour la boucle
Sumit Kumar Saha

1

Solution Java simple:

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

1
/*
* 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;
}

1

Voici une fonction 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));
    }

Utilisez-le comme ceci:

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

Il imprime:

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

La sortie est jolie
Merci

1

Voici un programme python pour trouver toutes les combinaisons d'argent. Il s'agit d'une solution de programmation dynamique avec un temps d'ordre (n). L'argent c'est 1,5,10,25

Nous passons de la ligne 1 à la ligne 25 (4 lignes). Row money 1 contient le décompte si l'on considère uniquement l'argent 1 dans le calcul du nombre de combinaisons. Row money 5 produit chaque colonne en prenant le compte dans la ligne money r pour le même argent final plus le compte 5 précédent dans sa propre ligne (position actuelle moins 5). L'argent en ligne 10 utilise l'argent en ligne 5, qui contient à la fois des comptes pour 1,5 et ajoute dans le compte 10 précédent (position actuelle moins 10). L'argent en ligne 25 utilise l'argent en ligne 10, qui contient les nombres pour l'argent en ligne 1,5,10 plus les 25 nombres précédents.

Par exemple, nombres [1] [12] = nombres [0] [12] + nombres [1] [7] (7 = 12-5) ce qui donne 3 = 1 + 2; nombres [3] [12] = nombres [2] [12] + nombres [3] [9] (-13 = 12-25) ce qui donne 4 = 0 + 4, puisque -13 est inférieur à 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)

0

Solution 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]);

}

}


0

La solution java ci-dessous qui imprimera également les différentes combinaisons. Facile à comprendre. L'idée est

pour somme 5

La solution est

    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......

Si la somme restante dans chaque boucle est inférieure à la dénomination, c'est-à-dire si la somme restante 1 est inférieure à 2, alors rompez simplement la boucle

Le code complet ci-dessous

Veuillez me corriger en cas d'erreur

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

}


0

Voici une solution basée sur python qui utilise la récursivité ainsi que la mémorisation résultant en une complexité de 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

D'accord, je doute que ce qui précède ait un temps d'exécution polynomial. Je ne sais pas si nous pouvons avoir un temps d'exécution polynomial. Mais ce que j'ai observé, c'est que la version ci-dessus fonctionne plus rapidement que la version non mémorisée dans de nombreux cas. Je vais continuer à chercher pourquoi
lalatnayak
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.