Cómo encontrar todas las combinaciones de monedas cuando se le da un valor en dólares

114

Encontré un fragmento de código que estaba escribiendo para la preparación de la entrevista hace unos meses.

Según el comentario que tenía, estaba intentando solucionar este problema:

Dado un valor en dólares en centavos (por ejemplo, 200 = 2 dólares, 1000 = 10 dólares), encuentre todas las combinaciones de monedas que forman el valor en dólares. Solo se permiten monedas de un centavo (1 ¢), monedas de cinco centavos (5 ¢), monedas de diez centavos (10 ¢) y veinticinco centavos (25 ¢).

Por ejemplo, si se dio 100, la respuesta debería ser:

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

Creo que esto se puede resolver tanto de forma iterativa como recursiva. Mi solución recursiva tiene bastantes errores y me preguntaba cómo otras personas resolverían este problema. La parte difícil de este problema fue hacerlo lo más eficiente posible.

codingbear
fuente
6
@akappa: centavo = 1 centavo; níquel = 5 centavos; dime = 10 centavos; trimestre = 25 centavos :)
codingbear
@John T: ¿código golf? ¡Nunca había oído hablar de ese término! De todos modos, espero ver algunas respuestas interesantes, ya que la comunidad SO puede resolver cualquier problema
codingbear
También intentaré publicar mi respuesta una vez que llegue a casa ... todavía en el trabajo y no debería pasar demasiado tiempo en SO.
codingbear
1
@blee code golf se refiere a resolver un problema en la menor cantidad de caracteres posible, con el lenguaje de programación de su elección. Aquí hay algunas que se han hecho en este sitio web: stackoverflow.com/search?q=code+golf
John T

Respuestas:

54

Investigué esto una vez hace mucho tiempo, y puedes leer mi pequeño artículo al respecto . Aquí está la fuente de Mathematica .

Mediante el uso de funciones generadoras, puede obtener una solución de tiempo constante de forma cerrada al problema. Concrete Mathematics de Graham, Knuth y Patashnik es el libro para esto, y contiene una discusión bastante extensa del problema. Básicamente, se define un polinomio donde el coeficiente n es el número de formas de realizar cambios por n dólares.

Las páginas 4-5 del informe muestran cómo puede usar Mathematica (o cualquier otro sistema de álgebra computacional conveniente) para calcular la respuesta de 10 ^ 10 ^ 6 dólares en un par de segundos en tres líneas de código.

(Y esto fue lo suficientemente largo como para que sean un par de segundos en un Pentium de 75Mhz ...)

andrewdotn
fuente
16
Buena respuesta, pero pequeñas objeciones: tenga en cuenta que (1) Esto da el número de formas, mientras que por alguna razón la pregunta pide el conjunto real de todas las formas. Por supuesto, no puede haber forma de encontrar el conjunto en tiempo polinómico, ya que la salida en sí misma tiene superpolinomialmente muchas entradas (2) Es discutible si una función generadora es una "forma cerrada" (ver el maravilloso libro de Herbert Wilf Generatingfunctionology : math. upenn.edu/~wilf/DownldGF.html ) y si te refieres a una expresión como (1 + √5) ^ n, se tarda Ω (log n) en calcular, no un tiempo constante.
ShreevatsaR
Introducción suave a la programación dinámica. Además, animo a cualquiera que tenga un problema de secuencia a leer la función de generación .
Coronel Panic
Muchas gracias Andrew ... esta explicación me ayudó mucho ... Publicando la función scala a continuación ... si alguien la necesita
jayaram S
1
Creo que la pregunta al principio necesita una ligera corrección porque pregunta "... ¿usando monedas de 1, 10, 25, 50 y 100 centavos?" Pero luego la redacción define el conjunto acomo el dominio de fpero a = {1,5,10,25,50,100}. Debe haber un 5 en la lista de monedas de un centavo. De lo contrario, la redacción fue fantástica, ¡gracias!
rbrtl
@rbrtl Wow, tienes razón, ¡gracias por darte cuenta! Lo actualizaré ...
andrewdotn
42

Nota : Esto solo muestra el número de formas.

Función 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)
Vlad
fuente
1
¿Existe realmente una forma de cambiar 0? Supongo que no hay forma de hacer eso.
Lucas
2
Proviene del número de soluciones polinomiales n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. Entonces, para money=0y coins=List(1,2,5,10)el recuento de combinaciones (n1, n2, n3, n4)es 1 y la solución es (0, 0, 0, 0).
Kyr
3
No puedo entender por qué funciona esta implementación. ¿Alguien puede explicarme el algoritmo detrás?
Adrien Lemaire
3
Esta es definitivamente la respuesta exacta al problema 3 del ejercicio 1 del curso Coursera Scala.
Justin Standard
Creo que, si money == 0pero coins.isEmpty, no debería contar como un sol'n. Por lo tanto, el algoritmo se puede servir mejor si la coins.isEmpty || money < 0condición se cumple primero.
juanchito
26

Favorecería una solución recursiva. Tiene una lista de denominaciones, si la más pequeña puede dividir uniformemente cualquier cantidad de moneda restante, esto debería funcionar bien.

Básicamente, te mueves de las denominaciones más grandes a las más pequeñas.
Recursivamente,

  1. Tiene un total actual para llenar y una denominación más grande (con más de 1 restante). Si solo queda 1 denominación, solo hay una forma de completar el total. Puede utilizar de 0 a k copias de su denominación actual de modo que k * denominación cur <= total.
  2. Para 0 a k, llame a la función con el total modificado y la nueva denominación más grande.
  3. Sume los resultados de 0 a k. Esa es la cantidad de formas en que puede completar su total desde la denominación actual hacia abajo. Devuelve este número.

Aquí está mi versión en Python de su problema declarado, por 200 centavos. Tengo 1463 maneras. Esta versión imprime todas las combinaciones y el total del recuento 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)
leif
fuente
No lo he ejecutado, pero siguiendo su lógica, tiene sentido :)
codingbear
Puede reemplazar las dos últimas líneas de la función con "return sum (count_combs (...) para ...)" - de esa manera la lista no se materializa en absoluto. :)
Nick Johnson
Gracias por el consejo. Siempre me interesan las formas de ajustar el código.
leif
2
Como se discutió en otra pregunta , este código dará un resultado incorrecto si la lista de denominationsno tiene 1como último valor. Puede agregar una pequeña cantidad de código al ifbloque más interno para solucionarlo (como describo en mi respuesta a la otra pregunta).
Blckknght
12

Función 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
}
jayaram S
fuente
¡Gracias! Esto es un gran comienzo. Pero, creo que esto falla cuando "dinero" comienza siendo 0 :).
aqn
10

Aquí hay un código C ++ absolutamente sencillo para resolver el problema que requería que se mostraran todas las combinaciones.

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

Pero estoy bastante intrigado por el subproblema de simplemente calcular el número de combinaciones. Sospecho que hay una ecuación de forma cerrada para ello.

George Phillips
fuente
9
Seguramente esto es C, no C ++.
nikhil
1
@George Phillips, ¿puedes explicarme?
Probar el
Creo que es bastante sencillo. Básicamente, la idea es iterar todos los cuartos (usando 0,1,2 .. máx.), Y luego iterar a través de todos los monedas de diez centavos según los cuartos usados, etc.
Peter Lee
4
La desventaja de esta solución es: si hay monedas de 50 centavos, 100 centavos, 500 centavos, entonces tenemos que usar bucles de 6 niveles ...
Peter Lee
3
Esto es bastante malo, si tiene denominaciones dinámicas o desea agregar otra denominación, esto no funcionará.
Shinzou
7

El problema secundario es un problema típico de programación dinámica.

/* 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;
}
Peter Lee
fuente
Sus soluciones dinámicas requieren que k sea la longitud de C menos 1. un poco confuso. Puede cambiarlo fácilmente para admitir la longitud real de C.
Idan
7

El código usa Java para resolver este problema y también funciona ... Este método puede no ser una buena idea debido a demasiados bucles, pero en realidad es una forma sencilla.

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

Esta es una pregunta realmente antigua, pero se me ocurrió una solución recursiva en java que parecía más pequeña que todas las demás, así que aquí va:

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

Mejoras:

  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);
        }
     }
Rohit Pandey
fuente
6

Sea C (i, J) el conjunto de combinaciones de hacer i centavos usando los valores del conjunto J.

Puede definir C como eso:

ingrese la descripción de la imagen aquí

(primero (J) toma de manera determinista un elemento de un conjunto)

Resulta una función bastante recursiva ... y razonablemente eficiente si usa la memorización;)

Akappa
fuente
Sí, esta ("programación dinámica", en cierto sentido) va a ser la solución óptima.
ShreevatsaR
tienes razón: toma J como una lista y no como un conjunto: luego primero (J) te trae el primer elemento y J \ first (J) te da el resto de la lista.
Akappa
¿Qué forma de matemáticas es esta?
Muhammad Umer
5

semi-hack para sortear el problema de combinación única - forzar orden descendente:

$ denominaciones = [1,5,10,25]
def all_combs (suma, último) 
  devuelve 1 si suma == 0
  return $ denominas.select {| d | d & le suma && d & le último} .inject (0) {| total, denom |
           total + todos_peines (suma-denom, denom)}
final

Esto funcionará lento ya que no se memorizará, pero entiendes la idea.

Klochner
fuente
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;
}
bjackfly
fuente
3

Esta es mi respuesta en Python. No usa recursividad:

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)

Salida de ejemplo

    ...
    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
marca
fuente
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);
}
jasonhao
fuente
2

Ambos: iterar a través de todas las denominaciones de mayor a menor, tomar una de denominación, restar del total requerido, luego recurrir al resto (restringiendo las denominaciones disponibles para que sean iguales o menores al valor de iteración actual).

djna
fuente
2

Si el sistema monetario lo permite, un simple algoritmo codicioso que toma la mayor cantidad posible de cada moneda, comenzando con la moneda de mayor valor.

De lo contrario, se requiere programación dinámica para encontrar una solución óptima rápidamente, ya que este problema es esencialmente el problema de la mochila .

Por ejemplo, si un sistema monetario tiene las monedas:, {13, 8, 1}la solución codiciosa haría el cambio por 24 as {13, 8, 1, 1, 1}, pero la verdadera solución óptima es{8, 8, 8}

Editar: Pensé que estábamos haciendo cambios de manera óptima, sin enumerar todas las formas de hacer cambios por un dólar. Mi entrevista reciente preguntó cómo hacer cambios, así que salté antes de terminar de leer la pregunta.

Ben S
fuente
el problema no es necesariamente de un dólar, podría ser de 2 o 23, por lo que su solución sigue siendo la única correcta.
Neil G
2

Sé que esta es una pregunta muy antigua. Estaba buscando la respuesta adecuada y no pude encontrar nada que sea simple y satisfactorio. Me tomó algo de tiempo pero pude anotar algo.

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

Esta es una solución de JavaScript y usa recursividad.

varun
fuente
Esta solución solo encuentra una denominación. La cuestión era encontrar "todas" las denominaciones.
heinob
2

En el lenguaje de programación Scala lo haría así:

 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)

       }

  }
MrOnyancha
fuente
2

Este es un algoritmo recursivo simple que toma un billete, luego toma un billete más pequeño de forma recursiva hasta que alcanza la suma, luego toma otro billete de la misma denominación y vuelve a recurrir. Consulte el resultado de muestra a continuación para ver una ilustración.

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 lo siguiente:

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
usuario7431997
fuente
1

Duh, me siento estúpido ahora mismo. A continuación hay una solución demasiado complicada, que preservaré porque es una solución, después de todo. Una solución simple sería esta:

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

Aquí está la otra solución. Esta solución se basa en la observación de que cada moneda es un múltiplo de las demás, por lo que se pueden representar en términos de ellas.

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

Entonces, para 37 monedas, por ejemplo:

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
Daniel C. Sobral
fuente
1

Esta entrada de blog resuelve este problema de mochila para las figuras de un cómic de XKCD . Un simple cambio en el itemsdict y el exactcostvalor arrojará todas las soluciones para su problema también.

Si el problema fuera encontrar el cambio que usó el menor costo, entonces un algoritmo ingenuo y codicioso que usara la mayor parte de la moneda de mayor valor podría fallar para algunas combinaciones de monedas y cantidad objetivo. Por ejemplo, si hay monedas con valores 1, 3 y 4; y la cantidad objetivo es 6, entonces el algoritmo codicioso podría sugerir tres monedas de valor 4, 1 y 1 cuando es fácil ver que podría usar dos monedas de valor 3 cada una.

  • Arrozal.
Paddy3118
fuente
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);                     
                }                   
            }                   
        }           
    }       
}
 }
Amit Patil
fuente
1

Encontré este código en el libro "Python para análisis de datos" de O'reily. Utiliza la implementación perezosa y la comparación int y supongo que se puede modificar para otras denominaciones usando decimales. ¡Déjame saber cómo te funciona!

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

Suhas
fuente
1

Esta es la mejora de la respuesta de Zihan. La gran cantidad de bucles innecesarios se produce cuando la denominación es de solo 1 centavo.

Es intuitivo y no recursivo.

    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;            
    }
Aerin
fuente
No puede generalizar esta solución, por lo que, por ejemplo, aparece un nuevo elemento en ese caso, debe agregar otro bucle for
Sumit Kumar Saha
1

Solución java sencilla:

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;
  }
}
GR44
fuente
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;
}
rpk
fuente
1

Aquí hay una función de 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));
    }

Úselo así:

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

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
Shinzou
fuente
La salida es bonita
Gracias
1

A continuación se muestra un programa de Python para encontrar todas las combinaciones de dinero. Esta es una solución de programación dinámica con orden (n) tiempo. El dinero es 1,5,10,25

Pasamos de la fila dinero 1 a la fila dinero 25 (4 filas). La fila de dinero 1 contiene el recuento si solo consideramos el dinero 1 al calcular el número de combinaciones. El dinero de la fila 5 produce cada columna tomando el recuento en el dinero de la fila r para el mismo dinero final más el recuento 5 anterior en su propia fila (posición actual menos 5). El dinero de la fila 10 usa el dinero de la fila 5, que contiene recuentos de 1,5 y suma en el recuento de 10 anteriores (posición actual menos 10). El dinero de la fila 25 usa el dinero de la fila 10, que contiene los recuentos del dinero de la fila 1,5,10 más el recuento de los 25 anteriores.

Por ejemplo, números [1] [12] = números [0] [12] + números [1] [7] (7 = 12-5) lo que da como resultado 3 = 1 + 2; números [3] [12] = números [2] [12] + números [3] [9] (-13 = 12-25) lo que resulta en 4 = 0 + 4, ya que -13 es menor que 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)
edW
fuente
0

Solución 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]);

}

}

El oso
fuente
0

La siguiente solución java que también imprimirá las diferentes combinaciones. Fácil de comprender. La idea es

por suma 5

La solucion es

    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 suma restante en cada ciclo es menor que la denominación, es decir, si la suma restante 1 es menor que 2, entonces simplemente rompa el ciclo.

El código completo a continuación

Por favor corríjame en caso de errores

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

}

PadreMathew
fuente
0

Aquí hay una solución basada en Python que usa recursividad y memorización, lo que resulta en una complejidad 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
lalatnayak
fuente
Bien, dudo que lo anterior tenga tiempo de ejecución polinomial. No estoy seguro de si podemos tener tiempo de ejecución polinomial. Pero lo que he observado es que lo anterior se ejecuta más rápido que la versión no memorizada en muchos casos. Continuaré investigando por qué
lalatnayak