Bir dolar değeri verildiğinde tüm madeni para kombinasyonları nasıl bulunur


114

Birkaç ay önce röportaj hazırlığı için yazdığım bir kod parçası buldum.

Yaptığım yoruma göre bu sorunu çözmeye çalışıyordu:

Sent cinsinden bir miktar dolar değeri verildiğinde (örneğin, 200 = 2 dolar, 1000 = 10 dolar), dolar değerini oluşturan tüm madeni para kombinasyonlarını bulun. Yalnızca kuruş (1 ¢), beş sent (5 ¢), on sent (10 ¢) ve çeyreklere (25 ¢) izin verilir.

Örneğin, 100 verilmişse cevap şöyle olmalıdır:

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

Bunun hem yinelemeli hem de özyinelemeli yollarla çözülebileceğine inanıyorum. Yinelemeli çözümüm oldukça hatalı ve diğer insanların bu sorunu nasıl çözeceğini merak ediyordum. Bu sorunun zor kısmı, onu olabildiğince verimli hale getirmekti.


6
@akappa: kuruş = 1 sent; nikel = 5 sent; on sent = 10 sent; çeyrek = 25 sent :)
codingbear

@John T: golf kodu? Bu terimi hiç duymadım! Her neyse, bazı ilginç cevaplar görmeyi umuyorum, çünkü SO topluluğu herhangi bir sorunu çözebilir
codingbear

Ayrıca eve geldiğimde cevabımı göndermeye çalışacağım ... hala işteyim ve SO'ya çok fazla zaman harcamamalıyım.
codingbear

1
@blee kodu golf, seçtiğiniz programlama diliyle mümkün olan en az sayıda karakterle bir sorunu çözmeyi ifade eder. İşte bu web sitesinde yapılanlardan bazıları: stackoverflow.com/search?q=code+golf
John T

Yanıtlar:


54

Bunu uzun zaman önce inceledim ve üzerine yazdıklarımı okuyabilirsiniz . İşte Mathematica kaynağı .

Oluşturma işlevlerini kullanarak, soruna kapalı formda sabit zamanlı bir çözüm elde edebilirsiniz. Graham, Knuth ve Patashnik's Concrete Mathematics bunun için kitaptır ve problemin oldukça kapsamlı bir tartışmasını içerir. Nerede Esasen bir polinom tanımlamak n inci katsayı için değişiklik yapma yolları sayısıdır n dolar.

Yazının 4-5. Sayfalarında Mathematica'yı (veya başka herhangi bir uygun bilgisayar cebir sistemini) üç satır kodda birkaç saniyede 10 ^ 10 ^ 6 dolar için nasıl kullanabileceğinizi gösterir.

(Ve bu yeterince uzun zaman önceydi, bu 75Mhz Pentium'da birkaç saniyeydi ...)


16
İyi cevap ancak küçük önemsiz: (1) Bu verdiğini not numarasını nedense soru, tüm yollar gerçek grubun sorar iken, yollarından. Elbette, polinom zamanında kümeyi bulmanın hiçbir yolu olamaz, çünkü çıktının kendisi süper polinomik olarak çok sayıda girdiye sahiptir (2) Bir üretici fonksiyonun "kapalı form" olup olmadığı tartışmalıdır (Herbert Wilf'in harika kitabı Generatingfunctionology : math. upenn.edu/~wilf/DownldGF.html ) ve (1 + √5) ^ n gibi bir ifadeyi kastediyorsanız, hesaplamak sabit zaman değil Ω (log n) zaman alır.
ShreevatsaR

Dinamik programlamaya nazik giriş. Ayrıca, sekans problemi olan herkesi üretme işlevini okumaya teşvik ederim .
Albay Panic

Çok teşekkürler Andrew ... bu açıklama bana çok yardımcı oldu ... Scala fonksiyonunu aşağıda
yayınlıyor

1
Başlangıçtaki sorunun hafif bir düzeltme gerektirdiğine inanıyorum çünkü "... 1-, 10-, 25-, 50- ve 100 sentlik madeni paralar mı kullanıyorsunuz?" Ancak daha sonra yazma, kümeyi but aetki alanı olarak tanımlar . Cent madeni paralar listesinde 5 olmalıdır. Aksi takdirde yazmak harikaydı, teşekkürler! fa = {1,5,10,25,50,100}
rbrtl

@rbrtl Vay canına, haklısın, bunu fark ettiğin için teşekkürler! Güncelleyeceğim…
andrewdotn

42

Not : Bu yalnızca yolların sayısını gösterir.

Scala işlevi:

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
0'ı değiştirmenin gerçekten bir yolu var mı? Sanırım bunu yapmanın bir yolu yok.
Luke

2
Polinom çözümlerinin sayısından kaynaklanır n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. Yani money=0ve coins=List(1,2,5,10)kombinasyonları için sayım (n1, n2, n3, n4)1'dir ve çözüm (0, 0, 0, 0).
Kyr

3
Kafamı bu uygulamanın neden işe yaradığını anlayamıyorum. Birisi bana arkasındaki algoritmayı açıklayabilir mi?
Adrien Lemaire

3
Bu kesinlikle coursera scala kursunun 1. alıştırmasının 3. probleminin kesin cevabıdır.
Justin Standart

İnanıyorum ki, eğer money == 0ama coins.isEmpty, bir sol'n olarak sayılmamalıdır. Bu nedenle, coins.isEmpty || money < 0durum ilk önce ck'd ise algo daha iyi servis edilebilir .
juanchito

26

Özyinelemeli bir çözümü tercih ederim. Bazı mezhepler listeniz var, en küçüğü kalan herhangi bir para birimini eşit olarak bölebilirse, bu iyi çalışmalıdır.

Temel olarak, en büyükten en küçüğe doğru ilerliyorsunuz.
Tekrarlı,

  1. Doldurmanız gereken bir cari toplamınız ve en büyük mezhebiniz (1'den fazla kaldı) var. Yalnızca 1 mezhep kaldıysa, toplamı doldurmanın tek bir yolu vardır. Geçerli mezhebinizin 0 ila k kopyasını kullanabilirsiniz, öyle ki k * cur değeri <= toplam.
  2. 0'dan k'ye kadar, işlevi değiştirilmiş toplam ve yeni en büyük değerle çağırın.
  3. 0'dan k'ye kadar olan sonuçları toplayın. Toplamınızı mevcut mezhepten aşağıya kadar doldurabileceğiniz kaç yol var. Bu numarayı geri verin.

İşte belirttiğiniz problemin 200 sente piton versiyonu. 1463 yol alıyorum. Bu sürüm tüm kombinasyonları ve son sayım toplamını yazdırır.

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


İşlevin son iki satırını "return sum (count_combs (...) for ...)" ile değiştirebilirsiniz - böylece liste hiç somutlaşmaz. :)
Nick Johnson

Bahşiş için teşekkürler. Her zaman kodu sıkılaştırmanın yollarıyla ilgilenirim.
leif

2
Başka bir soruda tartışıldığı gibi , listesinin son değeri denominationsyoksa , bu kod yanlış çıktı verecektir 1. En içteki ifbloğa, onu düzeltmek için az miktarda kod ekleyebilirsiniz (diğer soruya verdiğim cevabımda açıkladığım gibi).
Blckknght

12

Scala işlevi:

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
}

Teşekkürler! Bu harika bir başlangıç. Ancak, "para" 0 olmaya başladığında bu başarısız olur :).
aqn

10

İşte tüm kombinasyonların gösterilmesini gerektiren problemi çözmek için kesinlikle basit C ++ kodu.

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

Ama sadece kombinasyon sayısını hesaplamanın alt problemi beni oldukça meraklandırdı. Bunun için kapalı formda bir denklem olduğundan şüpheleniyorum.


9
Elbette bu C, C ++ değil.
nikhil

1
@George Phillips açıklayabilir misin?
deneniyor

Bence oldukça basit. Temel olarak, fikir, yinelemek için tüm (0,1,2 kullanarak .. maks) çeyrek ve sonra yinelemeyi kullanılan dörtte dayalı tüm Dimes geçer, vs ..
Peter Lee

4
Bu çözümün dezavantajı şudur: 50 sent, 100 sent, 500 sentlik madeni paralar varsa, 6 seviyeli döngüler kullanmak zorundayız ...
Peter Lee

3
Bu oldukça kötü, eğer dinamik bir mezhepiniz varsa veya başka bir mezhep eklemek istiyorsanız, o zaman bu işe yaramayacaktır.
shinzou

7

Alt problem, tipik bir Dinamik Programlama problemidir.

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

Dinamik çözümleriniz, k'nin C eksi 1 uzunluğunda olmasını gerektirir. Biraz kafa karıştırıcı. C'nin gerçek uzunluğunu desteklemek için kolayca değiştirebilirsiniz
Idan

7

Kod, bu sorunu çözmek için Java kullanıyor ve aynı zamanda işe yarıyor ... Bu yöntem, çok fazla döngü nedeniyle iyi bir fikir olmayabilir, ancak gerçekten basit bir yoldur.

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

Bu gerçekten eski bir soru, ancak java'da diğerlerinden daha küçük görünen yinelemeli bir çözüm buldum, işte burada -

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

İyileştirmeler:

  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

C (i, J), J kümesindeki değerleri kullanarak i sent yapma kombinasyonlarının kümesini alalım.

C'yi şu şekilde tanımlayabilirsiniz:

görüntü açıklamasını buraya girin

(ilk (J) deterministik bir şekilde bir kümenin bir öğesini alır )

Oldukça özyinelemeli bir işlev ortaya çıkıyor ... ve hafızaya alma kullanırsanız oldukça verimli;


Evet, bu (bir anlamda "dinamik programlama") en uygun çözüm olacak.
ShreevatsaR

haklısınız: J'yi bir küme olarak değil, bir liste olarak alın: sonra önce (J) size ilk elemanı getirir ve J \ ilk (J) size listenin geri kalanını verir.
akappa

bu ne tür bir matematik?
Muhammed Umer

5

benzersiz kombinasyon problemini aşmak için yarı-hack - azalan sıraya zorla:

$ para = [1,5,10,25]
def all_combs (toplam, son) 
  toplam == 0 ise 1 döndür
  $ denoms.select {| d | d & le toplamı && d & le son} .inject (0) {| toplam, değer |
           Toplam + all_combs (toplamı denom, denom)}
son

Bu, ezberlenmeyeceği için yavaş çalışacak, ancak fikri anladınız.


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

Python'daki cevabım bu. Özyineleme kullanmaz:

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)

Örnek çıktı

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

Her ikisi: yüksekten düşüğe tüm mezhepleri yineleyin, mezheplerden birini alın, gerekli toplamdan çıkartın, ardından kalan miktar üzerinde tekrarlayın (kullanılabilir mezhepleri mevcut yineleme değerine eşit veya daha düşük olacak şekilde sınırlayın.)


2

Para birimi sistemi izin veriyorsa, en yüksek değerli para biriminden başlayarak mümkün olduğunca çok parayı alan basit bir açgözlü algoritma .

Aksi takdirde, en iyi çözümü hızlı bir şekilde bulmak için dinamik programlama gerekir, çünkü bu problem esasen sırt çantası problemidir .

Örneğin, bir para sisteminde madeni paralar varsa:, {13, 8, 1}açgözlü çözüm 24 için değişiklik yapacaktır {13, 8, 1, 1, 1}, ancak gerçek en uygun çözüm{8, 8, 8}

Düzenleme: En iyi şekilde değişiklik yaptığımızı sanıyordum, bir dolar karşılığında değişiklik yapmanın tüm yollarını listelemiyoruz. Son röportajım nasıl değişiklik yapabileceğimi sordu, bu yüzden soruyu okumayı bitirmeden önce atladım.


sorun bir dolar için zorunlu değildir - 2 veya 23 olabilir, bu nedenle çözümünüz hala tek doğru olanıdır.
Neil G

2

Bunun çok eski bir soru olduğunu biliyorum. Doğru cevabı araştırıyordum ve basit ve tatmin edici hiçbir şey bulamadım. Biraz zaman aldı ama bir şeyi not almayı başardı.

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

Bu bir javascript çözümüdür ve özyinelemeyi kullanır.


Bu çözüm yalnızca bir mezhep bulur. Soru, "tüm" mezhepleri bulmaktı.
heinob

2

Scala Programlama dilinde bunu şu şekilde yapardım:

 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

Bu, bir faturayı alan, daha sonra toplama ulaşana kadar yinelemeli olarak daha küçük bir banknot alan, daha sonra aynı mezhebin başka bir faturasını alan ve tekrar yineleyen basit bir yinelemeli algoritmadır. Gösterim için aşağıdaki örnek çıktıya bakı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, "");

Aşağıdakileri yazdırır:

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

Şu anda kendimi aptal gibi hissediyorum. Aşağıda, koruyacağım aşırı karmaşık bir çözüm var çünkü , bir çözüm olduğu için bir çözüm var. Basit bir çözüm şu olabilir:

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

İşte diğer çözüm. Bu çözüm, her bir madalyonun diğerlerinin bir katı olduğu gözlemine dayanmaktadır, bu yüzden onlar açısından temsil edilebilirler.

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

Yani, 37 jeton için, örneğin:

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

Benim bu blog yazım , bir XKCD çizgi romanındaki figürler için bu sırt çantasına benzer problemi çözüyor . itemsDiktede ve exactcostdeğerde basit bir değişiklik , sorununuz için de tüm çözümleri sağlayacaktır.

Sorun, en düşük maliyeti kullanan değişikliği bulmaksa, o zaman en yüksek değerli madeni paranın çoğunu kullanan saf açgözlü bir algoritma, bazı madeni para ve hedef miktar kombinasyonları için pekala başarısız olabilir. Örneğin 1, 3 ve 4 değerlerine sahip madeni paralar varsa; ve hedef miktar 6 ise, açgözlü algoritma her biri 3 değerinde iki jeton kullanabileceğinizi görmek kolay olduğunda 4, 1 ve 1 değerinde üç jeton önerebilir.

  • Çeltik.

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

O'reily'nin "Python For Data Analysis" kitabında bu temiz kod parçasını buldum. Tembel uygulama ve int karşılaştırması kullanıyor ve ondalık sayılar kullanılarak diğer mezhepler için değiştirilebileceğini varsayıyorum. Sizin için nasıl çalıştığını bana bildirin!

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

Bu, Zihan'ın cevabının gelişmesidir. Mezhep sadece 1 sent olduğunda, gereksiz döngülerin çoğu gelir.

Sezgiseldir ve yinelemeli değildir.

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

Bu çözümü genelleyemezsiniz, bu nedenle örneğin yeni bir öğe ortaya çıkar bu durumda başka bir for döngüsü eklemeniz gerekir
Sumit Kumar Saha

1

Basit java çözümü:

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

İşte bir C # işlevi:

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

Bunu şu şekilde kullanın:

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

Aşağıdakileri yazdırır:

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

Çıktı güzel
Teşekkürler

1

Aşağıda tüm para kombinasyonlarını bulabileceğiniz bir python programı bulunmaktadır. Bu, sıra (n) zamanlı dinamik bir programlama çözümüdür. Para 1,5,10,25

1. sıradan 25. sıraya (4 sıra) geçiyoruz. Paragraf 1, kombinasyon sayısını hesaplarken yalnızca para 1'i dikkate alırsak sayımı içerir. Satır para 5, her bir sütunu, aynı son para için satır para r'deki sayımı artı kendi satırındaki önceki 5 sayımı (mevcut konum eksi 5) alarak üretir. Satır para 10, hem 1,5 için sayıları içeren hem de önceki 10 sayıma (mevcut konum eksi 10) ekleyen satır para 5'i kullanır. Satır para 25, satır parası 1,5,10 artı önceki 25 sayımını içeren satır parası 10'u kullanır.

Örneğin sayılar [1] [12] = sayılar [0] [12] + sayılar [1] [7] (7 = 12-5) 3 = 1 + 2 ile sonuçlanır; sayılar [3] [12] = sayılar [2] [12] + sayılar [3] [9] (-13 = 12-25) 4 = 0 + 4 ile sonuçlanır, çünkü -13 0'dan küçüktür.

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 çözümü

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

Farklı kombinasyonları da basacak aşağıdaki java çözümü. Anlaması kolay. Fikir

toplam 5 için

Çözüm şudur

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

Her döngüde kalan toplam değerden daha azsa, yani kalan toplam 1 2'den küçükse, o zaman döngüyü kırın

Aşağıdaki kodun tamamı

Lütfen herhangi bir hata durumunda beni düzeltin

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

Burada, O (mxn) karmaşıklığı ile sonuçlanan, özyinelemenin yanı sıra memoizasyonu kullanan python tabanlı bir çözüm var.

    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

Tamam, yukarıdakinin polinom çalışma süresine sahip olduğundan şüpheliyim. Polinom çalışma süresine sahip olabileceğimizden emin değiliz. Ancak gözlemlediğim şey, yukarıdaki bölümün çoğu durumda hatırlanmayan sürümden daha hızlı çalıştığı. Nedenini araştırmaya devam edeceğim
lalatnayak
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.