So finden Sie alle Kombinationen von Münzen, wenn Sie einen Dollarwert erhalten


114

Ich habe einen Code gefunden, den ich vor einigen Monaten für die Vorbereitung eines Interviews geschrieben habe.

Nach dem Kommentar, den ich hatte, wurde versucht, dieses Problem zu lösen:

Suchen Sie bei einem bestimmten Dollarwert in Cent (z. B. 200 = 2 Dollar, 1000 = 10 Dollar) alle Kombinationen von Münzen, aus denen der Dollarwert besteht. Es sind nur Pennys (1 ¢), Nickel (5 ¢), Groschen (10 ¢) und Viertel (25 ¢) zulässig.

Wenn beispielsweise 100 angegeben wurde, sollte die Antwort lauten:

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

Ich glaube, dass dies sowohl iterativ als auch rekursiv gelöst werden kann. Meine rekursive Lösung ist ziemlich fehlerhaft, und ich habe mich gefragt, wie andere Leute dieses Problem lösen würden. Der schwierige Teil dieses Problems bestand darin, es so effizient wie möglich zu gestalten.


6
@akappa: Penny = 1 Cent; Nickel = 5 Cent; Cent = 10 Cent; Quartal = 25 Cent :)
Codingbear

@ John T: Code Golf? Ich habe noch nie von diesem Begriff gehört! Wie auch immer, ich hoffe auf einige interessante Antworten, da die SO-Community jedes Problem lösen kann
Codingbear

Ich werde auch versuchen, meine Antwort zu posten, sobald ich nach Hause komme ... immer noch bei der Arbeit und ich sollte nicht zu viel Zeit mit SO verbringen.
Codingbear

1
@blee Code Golf bezieht sich auf das Lösen eines Problems mit möglichst wenig Zeichen und der Programmiersprache Ihrer Wahl. Hier sind einige, die auf dieser Website durchgeführt wurden: stackoverflow.com/search?q=code+golf
John T

Antworten:


54

Ich habe mich vor langer Zeit einmal damit befasst, und Sie können meine kleine Beschreibung darüber lesen . Hier ist die Mathematica-Quelle .

Durch die Verwendung von Generierungsfunktionen können Sie eine zeitlich konstante Lösung des Problems in geschlossener Form erhalten. Graham, Knuth und Patashniks Concrete Mathematics ist das Buch dafür und enthält eine ziemlich ausführliche Diskussion des Problems. Im Wesentlichen definieren Sie ein Polynom, bei dem der n- te Koeffizient die Anzahl der Änderungsmöglichkeiten für n Dollar ist.

Die Seiten 4 bis 5 der Beschreibung zeigen, wie Sie Mathematica (oder ein anderes praktisches Computeralgebrasystem) verwenden können, um die Antwort für 10 ^ 10 ^ 6 Dollar in wenigen Sekunden in drei Codezeilen zu berechnen.

(Und das war lange genug her, dass das auf einem 75-MHz-Pentium ein paar Sekunden sind ...)


16
Gute Antwort, aber kleinere Probleme: Beachten Sie, dass (1) dies die Anzahl der Wege angibt, während die Frage aus irgendeinem Grund nach der tatsächlichen Menge aller Wege fragt. Natürlich kann es keine Möglichkeit geben, die Menge in Polynomzeit zu finden, da die Ausgabe selbst superpolynomiell viele Einträge enthält. (2) Es ist fraglich, ob eine Erzeugungsfunktion eine "geschlossene Form" ist (siehe Herbert Wilfs wundervolles Buch Generatingfunctionology : math. upenn.edu/~wilf/DownldGF.html ) und wenn Sie einen Ausdruck wie (1 + √5) ^ n meinen, dauert die Berechnung Ω (log n), nicht die konstante Zeit.
ShreevatsaR

Schonende Einführung in die dynamische Programmierung. Außerdem ermutige ich jeden mit einem Sequenzproblem, die Generierungsfunktion zu lesen .
Colonel Panic

Vielen Dank Andrew ... diese Erklärung hat mir sehr geholfen ... Posting die Scala-Funktion unten ... sollte
jemand

1
Ich glaube, die Frage am Anfang muss leicht korrigiert werden, weil sie lautet: "... mit 1-, 10-, 25-, 50- und 100-Cent-Münzen?" Aber dann definiert das Aufschreiben die Menge aals die Domäne von faber a = {1,5,10,25,50,100}. Es sollte eine 5- in der Cent-Münzenliste geben. Ansonsten war das Schreiben fantastisch, danke!
rbrtl

@rbrtl Wow, du hast recht, danke, dass du das bemerkt hast! Ich werde es aktualisieren ...
andrewdotn

42

Hinweis : Hier wird nur die Anzahl der Möglichkeiten angezeigt.

Scala-Funktion:

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
Gibt es wirklich eine Möglichkeit, 0 zu ändern? Ich denke, es gibt keine Möglichkeit, das zu tun.
Luke

2
Es ergibt sich aus der Anzahl der Polynomlösungen n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. Also für money=0und coins=List(1,2,5,10)die Anzahl für Kombinationen (n1, n2, n3, n4)ist 1 und die Lösung ist (0, 0, 0, 0).
Kyr

3
Ich kann mir nicht vorstellen, warum diese Implementierung funktioniert. Kann mir jemand den Algorithmus dahinter erklären?
Adrien Lemaire

3
Dies ist definitiv die genaue Antwort auf Problem 3 von Übung 1 des Coursera-Scala-Kurses.
Justin Standard

Ich glaube, wenn money == 0doch coins.isEmpty, sollte es nicht als Sol'n gelten. Daher kann der Algo besser bedient werden, wenn die coins.isEmpty || money < 0Bedingung zuerst geprüft wird.
Juanchito

26

Ich würde eine rekursive Lösung bevorzugen. Sie haben eine Liste von Stückelungen. Wenn die kleinste den verbleibenden Währungsbetrag gleichmäßig aufteilen kann, sollte dies gut funktionieren.

Grundsätzlich wechseln Sie vom größten zum kleinsten Nennwert.
Rekursiv,

  1. Sie haben eine aktuelle Summe zu füllen und eine größte Stückelung (mit mehr als 1 übrig). Wenn nur noch 1 Stückelung übrig ist, gibt es nur eine Möglichkeit, die Summe zu füllen. Sie können 0 bis k Kopien Ihres aktuellen Nennwerts verwenden, sodass k * cur Nennwert <= gesamt ist.
  2. Rufen Sie für 0 bis k die Funktion mit der modifizierten Summe und dem neuen größten Nennwert auf.
  3. Addieren Sie die Ergebnisse von 0 bis k. Auf diese Weise können Sie Ihre Gesamtsumme ab dem aktuellen Nennwert abfüllen. Geben Sie diese Nummer zurück.

Hier ist meine Python-Version Ihres angegebenen Problems für 200 Cent. Ich bekomme 1463 Wege. Diese Version druckt alle Kombinationen und die endgültige Zählsumme.

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

Ich habe es nicht ausgeführt, aber wenn Sie Ihre Logik durchgehen, macht es Sinn :)
Codingbear

Sie können die letzten beiden Zeilen der Funktion durch "return sum (count_combs (...) for ...)" ersetzen - auf diese Weise wird die Liste überhaupt nicht materialisiert. :)
Nick Johnson

Danke für den Tipp. Ich bin immer an Möglichkeiten interessiert, den Code zu straffen.
Leif

2
Wie in einer anderen Frage erläutert , gibt dieser Code eine falsche Ausgabe aus, wenn die Liste von denominationsnicht 1den letzten Wert hat. Sie können dem innersten ifBlock eine kleine Menge Code hinzufügen, um das Problem zu beheben (wie in meiner Antwort auf die andere Frage beschrieben).
Blckknght

12

Scala-Funktion:

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
}

Vielen Dank! Dies ist ein guter Anfang. Aber ich denke, das schlägt fehl, wenn "Geld" anfängt, 0 zu sein :).
aqn

10

Hier ist ein absolut unkomplizierter C ++ - Code zur Lösung des Problems, bei dem alle Kombinationen angezeigt werden mussten.

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

Aber ich bin ziemlich fasziniert von dem Unterproblem, nur die Anzahl der Kombinationen zu berechnen. Ich vermute, es gibt eine geschlossene Gleichung dafür.


9
Dies ist sicherlich C, nicht C ++.
Nikil

1
@ George Phillips kannst du das erklären?
Versuch

Ich finde es ziemlich einfach. Grundsätzlich besteht die Idee darin, alle Viertel zu iterieren (mit maximal 0,1,2 ..) und dann alle Groschen basierend auf den verwendeten Vierteln usw. zu durchlaufen.
Peter Lee

4
Der Nachteil für diese Lösung ist: Wenn es 50-Cent-, 100-Cent-, 500-Cent-Münzen gibt, müssen wir 6-Level-Loops verwenden ...
Peter Lee

3
Dies ist ziemlich schlecht. Wenn Sie eine dynamische Bezeichnung haben oder eine andere Bezeichnung hinzufügen möchten, funktioniert dies nicht.
Shinzou

7

Das Unterproblem ist ein typisches Problem der dynamischen Programmierung.

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

Ihre dynamischen Lösungen erfordern, dass k die Länge von C minus 1 hat. Ein bisschen verwirrend. Sie können es leicht ändern, um die tatsächliche Länge von C zu unterstützen.
Idan

7

Der Code verwendet Java, um dieses Problem zu lösen, und es funktioniert auch ... Diese Methode ist möglicherweise wegen zu vieler Schleifen keine gute Idee, aber es ist wirklich ein einfacher Weg.

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

Dies ist eine wirklich alte Frage, aber ich habe eine rekursive Lösung in Java gefunden, die kleiner zu sein schien als alle anderen.

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

Verbesserungen:

  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

Sei C (i, J) die Menge der Kombinationen von i Cent unter Verwendung der Werte in der Menge J.

Sie können C wie folgt definieren:

Geben Sie hier die Bildbeschreibung ein

(first (J) nimmt deterministisch ein Element einer Menge auf)

Es stellt sich als ziemlich rekursive Funktion heraus ... und ziemlich effizient, wenn Sie Memoization verwenden;)


Ja, dies (in gewissem Sinne "dynamische Programmierung") wird die optimale Lösung sein.
ShreevatsaR

Sie haben Recht: Nehmen Sie J als Liste und nicht als Menge: Dann bringt Ihnen zuerst (J) das erste Element und J \ first (J) gibt Ihnen den Rest der Liste.
Akappa

Was für eine Form von Mathematik ist das?
Muhammad Umer

5

Semi-Hack, um das einzigartige Kombinationsproblem zu umgehen - absteigende Reihenfolge erzwingen:

$ denoms = [1,5,10,25]
def all_combs (Summe, letzte) 
  1 zurückgeben, wenn Summe == 0
  return $ denoms.select {| d | d & le sum && d & le last} .inject (0) {| total, denom |
           total + all_combs (sum-denom, denom)}
Ende

Dies läuft langsam, da es nicht gespeichert wird, aber Sie bekommen die Idee.


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

Dies ist meine Antwort in Python. Es wird keine Rekursion verwendet:

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)

Beispielausgabe

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

Beide: Durchlaufen Sie alle Nennwerte von hoch nach niedrig, nehmen Sie einen Nennwert, subtrahieren Sie von der erforderlichen Summe und wiederholen Sie den Rest (wobei die verfügbaren Nennwerte auf den aktuellen Iterationswert gleich oder niedriger sein müssen).


2

Wenn das Währungssystem dies zulässt, ein einfacher gieriger Algorithmus , der so viele Münzen wie möglich nimmt, beginnend mit der Währung mit dem höchsten Wert.

Andernfalls ist eine dynamische Programmierung erforderlich, um schnell eine optimale Lösung zu finden, da dieses Problem im Wesentlichen das Rucksackproblem ist .

Wenn zum Beispiel ein Währungssystem die Münzen {13, 8, 1}hat:, würde die gierige Lösung eine Änderung für 24 as vornehmen {13, 8, 1, 1, 1}, aber die wirklich optimale Lösung ist{8, 8, 8}

Bearbeiten: Ich dachte, wir würden Änderungen optimal vornehmen und nicht alle Möglichkeiten auflisten, um Änderungen für einen Dollar vorzunehmen. In meinem letzten Interview wurde gefragt, wie ich etwas ändern soll, also sprang ich voran, bevor ich mit dem Lesen der Frage fertig war.


Das Problem ist nicht unbedingt für einen Dollar - es könnte 2 oder 23 sein, also ist Ihre Lösung immer noch die einzig richtige.
Neil G

2

Ich weiß, dass dies eine sehr alte Frage ist. Ich habe nach der richtigen Antwort gesucht und konnte nichts finden, was einfach und zufriedenstellend ist. Ich habe einige Zeit gebraucht, konnte aber etwas aufschreiben.

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

Dies ist eine Javascript-Lösung und verwendet Rekursion.


Diese Lösung findet nur eine Stückelung. Die Frage war, "alle" Konfessionen zu finden.
Heinob

2

In der Programmiersprache Scala würde ich das so machen:

 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

Dies ist ein einfacher rekursiver Algorithmus, der eine Rechnung nimmt, dann eine kleinere Rechnung rekursiv nimmt, bis sie die Summe erreicht, dann eine andere Rechnung mit demselben Nennwert nimmt und erneut rekursiert. Zur Veranschaulichung siehe Beispielausgabe unten.

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

Druckt Folgendes:

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, ich fühle mich gerade dumm. Unten gibt es eine übermäßig komplizierte Lösung, die ich erhalten werde , weil es ist eine Lösung, nachdem alle. Eine einfache Lösung wäre folgende:

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

Hier ist die andere Lösung. Diese Lösung basiert auf der Beobachtung, dass jede Münze ein Vielfaches der anderen ist, sodass sie in Bezug auf sie dargestellt werden können.

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

Also für 37 Münzen zum Beispiel:

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

Dieser Blogeintrag von mir löst dieses rucksackartige Problem für die Figuren aus einem XKCD-Comic . Eine einfache Änderung des itemsDiktats und des exactcostWerts liefert auch alle Lösungen für Ihr Problem.

Wenn das Problem darin besteht, die Änderung zu finden, die die geringsten Kosten verursacht, kann ein naiver, gieriger Algorithmus, der so viel wie möglich von der Münze mit dem höchsten Wert verwendet, bei einigen Kombinationen von Münzen und Zielbetrag fehlschlagen. Zum Beispiel, wenn es Münzen mit den Werten 1, 3 und 4 gibt; und der Zielbetrag ist 6, dann schlägt der gierige Algorithmus möglicherweise drei Münzen mit den Werten 4, 1 und 1 vor, wenn leicht zu erkennen ist, dass Sie jeweils zwei Münzen mit dem Wert 3 verwenden können.

  • 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

Ich habe diesen ordentlichen Code in dem Buch "Python For Data Analysis" von O'reily gefunden. Es verwendet eine verzögerte Implementierung und einen int-Vergleich und ich nehme an, dass es mit Dezimalstellen für andere Nennwerte geändert werden kann. Lassen Sie mich wissen, wie es bei Ihnen funktioniert!

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

Dies ist die Verbesserung von Zihans Antwort. Die vielen unnötigen Schleifen entstehen, wenn der Nennwert nur 1 Cent beträgt.

Es ist intuitiv und nicht rekursiv.

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

u kann diese Lösung nicht verallgemeinern, so dass zum Beispiel ein neues Element auftaucht. In diesem Fall müssen Sie ein weiteres für die Schleife hinzufügen
Sumit Kumar Saha

1

Einfache Java-Lösung:

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

Hier ist eine C # -Funktion:

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

Verwenden Sie es so:

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

Es druckt:

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

Die Ausgabe ist hübsch
Danke

1

Unten finden Sie ein Python-Programm, mit dem Sie alle Geldkombinationen finden können. Dies ist eine dynamische Programmierlösung mit Auftragszeit (n). Geld ist 1,5,10,25

Wir wechseln von Zeilengeld 1 zu Zeilengeld 25 (4 Zeilen). Zeilengeld 1 enthält die Anzahl, wenn wir bei der Berechnung der Anzahl der Kombinationen nur Geld 1 berücksichtigen. Zeilengeld 5 erzeugt jede Spalte, indem die Zählung in Zeilengeld r für dasselbe Endgeld plus die vorherigen 5 Zählungen in der eigenen Zeile (aktuelle Position minus 5) vorgenommen wird. Zeilengeld 10 verwendet Zeilengeld 5, das Zählungen für beide 1,5 enthält und die vorherigen 10 Zählungen addiert (aktuelle Position minus 10). Zeilengeld 25 verwendet Zeilengeld 10, das Zählungen für Zeilengeld 1,5,10 plus die vorherigen 25 Zählungen enthält.

Zum Beispiel Zahlen [1] [12] = Zahlen [0] [12] + Zahlen [1] [7] (7 = 12-5), was zu 3 = 1 + 2 führt; Zahlen [3] [12] = Zahlen [2] [12] + Zahlen [3] [9] (-13 = 12-25), was zu 4 = 0 + 4 führt, da -13 kleiner als 0 ist.

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

Java-Lösung

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

Die folgende Java-Lösung, die auch die verschiedenen Kombinationen druckt. Einfach zu verstehen. Idee ist

für Summe 5

Die Lösung ist

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

Wenn die verbleibende Summe in jeder Schleife kleiner als der Nennwert ist, dh wenn die verbleibende Summe 1 kleiner als 2 ist, brechen Sie einfach die Schleife

Der vollständige Code unten

Bitte korrigieren Sie mich im Fehlerfall

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

Hier ist eine Python-basierte Lösung, die sowohl Rekursion als auch Memoisierung verwendet, was zu einer Komplexität von O (mxn) führt.

    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

Okay, ich bezweifle, dass das oben Genannte eine Polynomlaufzeit hat. Ich bin mir nicht sicher, ob wir überhaupt eine Polynomlaufzeit haben können. Was ich jedoch beobachtet habe, ist, dass das oben Genannte in vielen Fällen schneller läuft als die nicht gespeicherte Version. Ich werde weiter erforschen, warum
lalatnayak
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.