Come trovare tutte le combinazioni di monete quando viene fornito un valore in dollari


114

Ho trovato un pezzo di codice che stavo scrivendo per la preparazione del colloquio pochi mesi fa.

Secondo il commento che ho avuto, stava cercando di risolvere questo problema:

Dato un valore in dollari in centesimi (ad esempio 200 = 2 dollari, 1000 = 10 dollari), trova tutte le combinazioni di monete che compongono il valore in dollari. Sono consentiti solo penny (1 ¢), nickel (5 ¢), monetine (10 ¢) e quarti (25 ¢).

Ad esempio, se è stato fornito 100, la risposta dovrebbe essere:

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

Credo che questo possa essere risolto sia in modo iterativo che ricorsivo. La mia soluzione ricorsiva è piuttosto buggata e mi chiedevo come altre persone avrebbero risolto questo problema. La parte difficile di questo problema era renderlo il più efficiente possibile.


6
@akappa: penny = 1 cent; nichel = 5 centesimi; centesimo = 10 centesimi; quarto = 25 centesimi :)
codingbear

@ John T: codice di golf? Non ho mai sentito parlare di quel termine! Ad ogni modo, spero di vedere alcune risposte interessanti, dal momento che la comunità SO può risolvere qualsiasi problema
codingbear

Cercherò anche di pubblicare la mia risposta una volta tornato a casa ... ancora al lavoro e non dovrei passare troppo tempo su SO.
codingbear

1
@blee code golf si riferisce alla risoluzione di un problema con il minor numero di caratteri possibile, con il linguaggio di programmazione che preferisci. Eccone alcuni che sono stati fatti su questo sito Web: stackoverflow.com/search?q=code+golf
John T

Risposte:


54

Ho esaminato questo aspetto una volta molto tempo fa e puoi leggere il mio piccolo articolo su di esso . Ecco la fonte di Mathematica .

Utilizzando le funzioni di generazione, è possibile ottenere una soluzione a tempo costante in forma chiusa del problema. Concrete Mathematics di Graham, Knuth e Patashnik è il libro adatto a questo e contiene una discussione abbastanza ampia del problema. Essenzialmente si definisce un polinomio in cui il coefficiente n- esimo è il numero di modi per apportare modifiche per n dollari.

Le pagine 4-5 del documento mostrano come puoi usare Mathematica (o qualsiasi altro comodo sistema di computer algebra) per calcolare la risposta per 10 ^ 10 ^ 6 dollari in un paio di secondi in tre righe di codice.

(E questo è stato abbastanza tempo fa che sono passati un paio di secondi su un Pentium a 75 Mhz ...)


16
Buona risposta, ma piccoli cavilli: nota che (1) Questo fornisce il numero di modi, mentre per qualche motivo la domanda richiede l'insieme effettivo di tutti i modi. Naturalmente, non ci può essere modo di trovare l'insieme in tempo polinomiale, poiché l'output stesso ha molte voci in modo superpolinomiale (2) È discutibile se una funzione generatrice sia una "forma chiusa" (vedi il meraviglioso libro di Herbert Wilf Generatingfunctionology : math. upenn.edu/~wilf/DownldGF.html ) e se intendi un'espressione come (1 + √5) ^ n, ci vuole Ω (log n) tempo per calcolare, non tempo costante.
ShreevatsaR

Gentile introduzione alla programmazione dinamica. Inoltre, incoraggio chiunque abbia un problema di sequenza a leggere la funzione di generazione .
Colonel Panic

Grazie mille Andrew ... questa spiegazione mi ha aiutato così tanto ... Pubblicando la funzione scala di seguito .. se qualcuno ne avesse bisogno
jayaram S

1
Credo che la domanda all'inizio necessiti di una leggera correzione perché chiede "... usando monete da 1, 10, 25, 50 e 100 cent?" Ma poi la scrittura definisce l'insieme acome il dominio di fma a = {1,5,10,25,50,100}. Ci dovrebbe essere un 5 nella lista delle monete da cento. Altrimenti la recensione è stata fantastica, grazie!
rbrtl

@rbrtl Wow, hai ragione, grazie per averlo notato! Lo aggiornerò ...
andrewdotn

42

Nota : questo mostra solo il numero di modi.

Funzione 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
C'è davvero un modo per cambiare 0? Immagino che non ci sia modo per farlo.
Luca

2
Deriva dal numero di soluzioni polinomiali n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. Quindi per money=0e coins=List(1,2,5,10)il conteggio delle combinazioni (n1, n2, n3, n4)è 1 e la soluzione è (0, 0, 0, 0).
Kyr

3
Non riesco a capire perché questa implementazione funziona. Qualcuno può spiegarmi l'algoritmo dietro?
Adrien Lemaire

3
Questa è sicuramente la risposta esatta al problema 3 dell'esercizio 1 del corso di coursera scala.
Justin Standard

Credo che, se money == 0ma coins.isEmpty, non dovrebbe contare come un sol'n. Pertanto, l'algoritmo può essere servito meglio se la coins.isEmpty || money < 0condizione viene verificata per prima.
juanchito

26

Preferirei una soluzione ricorsiva. Hai un elenco di denominazioni, se il più piccolo può dividere equamente qualsiasi importo in valuta rimanente, dovrebbe funzionare bene.

Fondamentalmente, passi dalle denominazioni più grandi a quelle più piccole.
ricorsivamente,

  1. Hai un totale corrente da riempire e una denominazione più grande (con più di 1 rimanente). Se è rimasta solo 1 denominazione, c'è solo un modo per riempire il totale. Puoi usare da 0 a k copie della tua denominazione corrente in modo tale che k * cur denomination <= total.
  2. Da 0 a k, chiama la funzione con il totale modificato e la nuova denominazione più grande.
  3. Somma i risultati da 0 a k. Ecco in quanti modi puoi riempire il tuo totale dalla denominazione attuale in giù. Restituisci questo numero.

Ecco la mia versione in Python del tuo problema dichiarato, per 200 centesimi. Ottengo 1463 modi. Questa versione stampa tutte le combinazioni e il totale del conteggio finale.

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

Non l'ho eseguito, ma seguendo la tua logica, ha senso :)
codingbear

Puoi sostituire le ultime due righe della funzione con "return sum (count_combs (...) for ...)" - in questo modo l'elenco non si materializza affatto. :)
Nick Johnson

Grazie per il consiglio. Sono sempre interessato ai modi per rafforzare il codice.
leif

2
Come discusso in un'altra domanda , questo codice darà un output non corretto se l'elenco di denominationsnon ha 1come ultimo valore. Puoi aggiungere una piccola quantità di codice al ifblocco più interno per risolverlo (come descrivo nella mia risposta all'altra domanda).
Blckknght

12

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

Grazie! Questo è un ottimo inizio. Ma penso che questo fallisca quando "money" inizia a essere 0 :).
aqn

10

Ecco del codice C ++ assolutamente semplice per risolvere il problema che richiedeva la visualizzazione di tutte le combinazioni.

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

Ma sono piuttosto incuriosito dal problema secondario del solo calcolo del numero di combinazioni. Sospetto che ci sia un'equazione in forma chiusa per questo.


9
Sicuramente questo è C, non C ++.
nikhil

1
@George Phillips puoi spiegare?
Prova il

Penso che sia abbastanza semplice. Fondamentalmente, l'idea è di iterare tutti i trimestri (utilizzando 0,1,2 .. max), quindi iterare tutti i decimi in base ai quarti utilizzati, ecc.
Peter Lee

4
Lo svantaggio di questa soluzione è: se ci sono monete da 50 centesimi, 100 cent, 500 cent, allora dobbiamo usare loop a 6 livelli ...
Peter Lee

3
Questo è piuttosto brutto, se hai una denominazione dinamica o vuoi aggiungere un'altra denominazione, allora non funzionerà.
shinzou

7

Il problema secondario è un tipico problema di programmazione dinamica.

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

Le tue soluzioni dinamiche richiedono che k sia la lunghezza di C meno 1. un po 'confuso. Puoi cambiarlo facilmente per supportare la lunghezza reale di C.
Idan

7

Il codice utilizza Java per risolvere questo problema e funziona anche ... Questo metodo potrebbe non essere una buona idea a causa dei troppi loop, ma è davvero un modo semplice.

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

Questa è una domanda molto vecchia, ma ho trovato una soluzione ricorsiva in java che sembrava più piccola di tutte le altre, quindi ecco qui -

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

miglioramenti:

  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

Sia C (i, J) l'insieme di combinazioni per fare i cent usando i valori dell'insieme J.

Puoi definire C come quello:

inserisci qui la descrizione dell'immagine

(la prima (J) prende in modo deterministico un elemento di un insieme)

Risulta una funzione piuttosto ricorsiva ... e ragionevolmente efficiente se usi la memoizzazione;)


Sì, questa ("programmazione dinamica", in un certo senso) sarà la soluzione ottimale.
ShreevatsaR

hai ragione: prendi J come una lista e non come un insieme: poi prima (J) ti porta il primo elemento e J \ first (J) ti dà il resto della lista.
akappa

che forma di matematica è questa?
Muhammad Umer

5

semi-hack per aggirare il problema della combinazione unica: forza l'ordine discendente:

$ denomi = [1,5,10,25]
def all_combs (sum, last) 
  restituisce 1 se somma == 0
  return $ denoms.select {| d | d & le sum && d & le last} .inject (0) {| total, denom |
           Totale all_combs + (somma-denom, denom)}
fine

Funzionerà lentamente poiché non verrà memorizzato, ma hai un'idea.


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

Questa è la mia risposta in Python. Non usa la ricorsione:

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)

Output di esempio

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

Entrambi: iterare attraverso tutte le denominazioni dal più alto al più basso, prendere uno di denominazione, sottrarre dal totale richiesto, quindi ricorrere al resto (vincolando le denominazioni disponibili ad essere uguali o inferiori al valore di iterazione corrente.)


2

Se il sistema valutario lo consente, un semplice algoritmo avido che prende il maggior numero possibile di monete, partendo dalla valuta di valore più alto.

Altrimenti, è necessaria la programmazione dinamica per trovare rapidamente una soluzione ottimale poiché questo problema è essenzialmente il problema dello zaino .

Ad esempio, se un sistema di valuta ha le monete {13, 8, 1}:, la soluzione avida farebbe cambiare per 24 come {13, 8, 1, 1, 1}, ma la vera soluzione ottimale è{8, 8, 8}

Modifica: pensavo che stessimo apportando modifiche in modo ottimale, non elencando tutti i modi per apportare modifiche per un dollaro. La mia recente intervista ha chiesto come apportare modifiche, quindi sono andato avanti prima di finire di leggere la domanda.


il problema non è necessariamente per un dollaro - potrebbe 2 o 23, quindi la tua soluzione è ancora l'unica corretta.
Neil G

2

So che questa è una domanda molto vecchia. Stavo cercando la risposta corretta e non sono riuscito a trovare nulla di semplice e soddisfacente. Mi ci è voluto del tempo ma sono riuscito a buttare giù qualcosa.

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

Questa è una soluzione javascript e utilizza la ricorsione.


Questa soluzione trova solo una denominazione. La domanda era trovare "tutte" le denominazioni.
heinob

2

Nel linguaggio di programmazione Scala lo farei in questo modo:

 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

Questo è un semplice algoritmo ricorsivo che prende una banconota, quindi prende una banconota più piccola in modo ricorsivo fino a raggiungere la somma, poi prende un'altra banconota dello stesso taglio e ricorre di nuovo. Vedere l'output di esempio di seguito per l'illustrazione.

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

Stampa quanto segue:

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, mi sento stupido adesso. Di seguito c'è una soluzione eccessivamente complicata, che preserverò perché è una soluzione, dopotutto. Una soluzione semplice sarebbe questa:

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

Ecco l'altra soluzione. Questa soluzione si basa sull'osservazione che ogni moneta è un multiplo delle altre, quindi possono essere rappresentate in termini di esse.

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

Quindi, per 37 monete, ad esempio:

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

Questo mio post nel blog risolve questo problema simile a uno zaino per le figure di un fumetto XKCD . Una semplice modifica al itemsdict e alexactcost valore produrrà tutte le soluzioni anche per il tuo problema.

Se il problema fosse trovare la modifica che utilizza il minor costo, un ingenuo algoritmo avido che utilizza la maggior parte della moneta di valore più alto potrebbe fallire per alcune combinazioni di monete e importo target. Ad esempio se ci sono monete con valori 1, 3 e 4; e l'importo target è 6, l'algoritmo avido potrebbe suggerire tre monete di valore 4, 1 e 1 quando è facile vedere che è possibile utilizzare due monete ciascuna di valore 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

Ho trovato questo bel pezzo di codice nel libro "Python For Data Analysis" di O'reily. Utilizza un'implementazione pigra e un confronto int e presumo che possa essere modificato per altre denominazioni utilizzando i decimali. Fammi sapere come funziona per te!

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

Questo è il miglioramento della risposta di Zihan. La grande quantità di loop non necessari arriva quando la denominazione è solo di 1 centesimo.

È intuitivo e non ricorsivo.

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

Non puoi generalizzare questa soluzione, quindi ad esempio viene fuori un nuovo elemento in quel caso devi aggiungere un altro ciclo for
Sumit Kumar Saha

1

Soluzione Java semplice:

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

Ecco una funzione 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));
    }

Usalo in questo modo:

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

Stampa:

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

L'output è carino
Grazie

1

Di seguito è riportato un programma Python per trovare tutte le combinazioni di denaro. Questa è una soluzione di programmazione dinamica con tempo di ordine (n). Il denaro è 1,5,10,25

Si passa dalla riga money 1 alla riga money 25 (4 righe). La riga denaro 1 contiene il conteggio se consideriamo solo denaro 1 nel calcolo del numero di combinazioni. La riga denaro 5 produce ciascuna colonna prendendo il conteggio nella riga denaro r per lo stesso denaro finale più il conteggio 5 precedente nella propria riga (posizione corrente meno 5). Il denaro della riga 10 utilizza il denaro della riga 5, che contiene i conteggi sia per 1,5 che per il conteggio dei 10 precedenti (posizione corrente meno 10). Il denaro di riga 25 utilizza il denaro di riga 10, che contiene i conteggi per il denaro di riga 1,5,10 più il conteggio precedente di 25.

Ad esempio, numeri [1] [12] = numeri [0] [12] + numeri [1] [7] (7 = 12-5) che risulta in 3 = 1 + 2; numeri [3] [12] = numeri [2] [12] + numeri [3] [9] (-13 = 12-25) che risulta in 4 = 0 + 4, poiché -13 è minore di 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

Soluzione 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 seguente soluzione java che stamperà anche le diverse combinazioni. Facile da capire. L'idea è

per somma 5

La soluzione è

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

Se la somma rimanente in ogni ciclo è inferiore alla denominazione, cioè se la somma rimanente 1 è minore di 2, interrompi semplicemente il ciclo

Il codice completo di seguito

Per favore correggimi in caso di errori

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

Ecco una soluzione basata su Python che utilizza la ricorsione e la memoizzazione risultando in una complessità di 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

Ok, dubito che quanto sopra abbia un tempo di esecuzione polinomiale. Non sono sicuro che possiamo avere un tempo di esecuzione polinomiale. Ma quello che ho osservato è che in molti casi quanto sopra funziona più velocemente della versione non memorizzata. Continuerò a ricercare il motivo
lalatnayak
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.