Bagaimana menemukan semua kombinasi koin ketika diberi nilai dolar

114

Saya menemukan sepotong kode yang saya tulis untuk persiapan wawancara beberapa bulan lalu.

Menurut komentar yang saya miliki, itu mencoba menyelesaikan masalah ini:

Diberikan beberapa nilai dolar dalam sen (misalnya 200 = 2 dolar, 1000 = 10 dolar), temukan semua kombinasi koin yang membentuk nilai dolar. Hanya ada uang receh (1 ¢), nikel (5 ¢), dime (10 ¢), dan quarters (25 ¢) yang diperbolehkan.

Misalnya, jika diberikan 100, jawabannya adalah:

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

Saya percaya bahwa ini dapat diselesaikan dengan cara berulang dan rekursif. Solusi rekursif saya cukup bermasalah, dan saya bertanya-tanya bagaimana orang lain akan menyelesaikan masalah ini. Bagian yang sulit dari masalah ini adalah membuatnya seefisien mungkin.

codingbear
sumber
6
@akappa: penny = 1 sen; nikel = 5 sen; uang receh = 10 sen; seperempat = 25 sen :)
codingbear
@ John T: kode golf? Saya belum pernah mendengar istilah itu! Bagaimanapun, saya berharap untuk melihat beberapa jawaban yang menarik, karena komunitas SO dapat menyelesaikan masalah apa pun
codingbear
Saya juga akan mencoba memposting jawaban saya setelah saya pulang ... masih bekerja dan saya tidak boleh menghabiskan terlalu banyak waktu untuk SO.
codingbear
1
@lee code golf mengacu pada pemecahan masalah dalam jumlah karakter sesedikit mungkin, dengan bahasa pemrograman pilihan Anda. Berikut adalah beberapa yang telah dilakukan di situs web ini: stackoverflow.com/search?q=code+golf
John T

Jawaban:

54

Saya melihat ini sekali lama sekali, dan Anda dapat membaca tulisan kecil saya tentangnya . Berikut sumber Mathematica .

Dengan menggunakan fungsi pembangkit, Anda bisa mendapatkan solusi waktu-konstan bentuk-tertutup untuk masalah tersebut. Graham, Knuth, dan Patashnik's Concrete Mathematics adalah buku untuk ini, dan berisi pembahasan masalah yang cukup ekstensif. Pada dasarnya Anda mendefinisikan polinomial di mana koefisien ke- n adalah jumlah cara membuat perubahan untuk n dolar.

Halaman 4-5 dari artikel ini menunjukkan bagaimana Anda dapat menggunakan Mathematica (atau sistem aljabar komputer lain yang nyaman) untuk menghitung jawaban 10 ^ 10 ^ 6 dolar dalam beberapa detik dalam tiga baris kode.

(Dan ini sudah cukup lama sehingga hanya beberapa detik pada Pentium 75Mhz ...)

andrewdotn
sumber
16
Jawaban bagus, tetapi pertanyaan kecil: perhatikan bahwa (1) Ini memberikan jumlah cara, sementara untuk beberapa alasan pertanyaan menanyakan himpunan sebenarnya dari semua cara. Tentu saja, tidak ada cara untuk menemukan himpunan dalam waktu polinomial, karena keluarannya sendiri memiliki banyak entri superpolinomial (2) Masih diperdebatkan apakah fungsi pembangkit adalah "bentuk tertutup" (lihat buku bagus Herbert Wilf, Generatingfunctionology : math. upenn.edu/~wilf/DownldGF.html ) dan jika yang Anda maksud adalah ekspresi seperti (1 + √5) ^ n, diperlukan waktu Ω (log n) untuk menghitung, bukan waktu yang konstan.
ShreevatsaR
Pengenalan lembut untuk pemrograman dinamis. Juga, saya mendorong siapa pun dengan masalah urutan untuk membaca fungsiologi pembangkit .
Kolonel Panic
Terima kasih banyak Andrew ... penjelasan ini sangat membantu saya ... Memposting fungsi scala di bawah ini .. haruskah ada orang yang membutuhkannya
jayaram S
1
Saya percaya pertanyaan di awal membutuhkan sedikit koreksi karena menanyakan "... menggunakan koin 1-, 10-, 25-, 50-, dan 100 sen?" Tapi kemudian penulisan mendefinisikan set asebagai domain dari ftapi a = {1,5,10,25,50,100}. Harus ada 5 dalam daftar koin sen. Kalau tidak, tulisannya fantastis, terima kasih!
rbrtl
@rbrtl Wow, Anda benar, terima kasih telah memperhatikannya! Saya akan memperbaruinya…
andrewdotn
42

Catatan : Ini hanya menunjukkan beberapa cara.

Fungsi skala:

def countChange(money: Int, coins: List[Int]): Int =
  if (money == 0) 1
  else if (coins.isEmpty || money < 0) 0
  else countChange(money - coins.head, coins) + countChange(money, coins.tail)
Vlad
sumber
1
Benarkah ada satu cara untuk mengubah 0? Saya kira tidak ada cara untuk melakukan itu.
Lukas
2
Ini berasal dari jumlah solusi polinomial n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. Jadi untuk money=0dan coins=List(1,2,5,10)jumlah kombinasi (n1, n2, n3, n4)adalah 1 dan solusinya adalah (0, 0, 0, 0).
Kyr
3
Saya tidak dapat memahami mengapa penerapan ini berhasil. Adakah yang bisa menjelaskan algoritme di baliknya?
Adrien Lemaire
3
Ini jelas merupakan jawaban yang tepat untuk masalah 3 dari latihan 1 dari kursus coursera scala.
Justin Standard
Saya percaya bahwa, jika money == 0tetapi coins.isEmpty, itu tidak dihitung sebagai sol'n. Oleh karena itu, algo mungkin lebih baik disajikan jika coins.isEmpty || money < 0kondisinya ditentukan terlebih dahulu.
juanchito
26

Saya lebih menyukai solusi rekursif. Anda memiliki beberapa daftar denominasi, jika yang terkecil dapat membagi jumlah mata uang yang tersisa secara merata, ini akan berfungsi dengan baik.

Pada dasarnya, Anda berpindah dari denominasi terbesar ke terkecil.
Secara rekursif,

  1. Anda memiliki jumlah saat ini untuk diisi, dan denominasi terbesar (dengan lebih dari 1 tersisa). Jika hanya tersisa 1 pecahan, maka hanya ada satu cara untuk mengisi totalnya. Anda dapat menggunakan 0 hingga k salinan dari pecahan Anda saat ini sehingga k * cur denominasi <= total.
  2. Untuk 0 sampai k, panggil fungsi dengan total modifikasi dan denominasi terbesar baru.
  3. Jumlahkan hasil dari 0 sampai k. Begitulah cara Anda mengisi total dari pecahan saat ini ke bawah. Kembalikan nomor ini.

Ini versi python saya dari masalah yang Anda nyatakan, seharga 200 sen. Saya mendapatkan 1463 cara. Versi ini mencetak semua kombinasi dan total hitungan akhir.

#!/usr/bin/python

# find the number of ways to reach a total with the given number of combinations

cents = 200
denominations = [25, 10, 5, 1]
names = {25: "quarter(s)", 10: "dime(s)", 5 : "nickel(s)", 1 : "pennies"}

def count_combs(left, i, comb, add):
    if add: comb.append(add)
    if left == 0 or (i+1) == len(denominations):
        if (i+1) == len(denominations) and left > 0:
           if left % denominations[i]:
               return 0
           comb.append( (left/denominations[i], demoninations[i]) )
           i += 1
        while i < len(denominations):
            comb.append( (0, denominations[i]) )
            i += 1
        print(" ".join("%d %s" % (n,names[c]) for (n,c) in comb))
        return 1
    cur = denominations[i]
    return sum(count_combs(left-x*cur, i+1, comb[:], (x,cur)) for x in range(0, int(left/cur)+1))

count_combs(cents, 0, [], None)

leif
sumber
Belum menjalankannya, tetapi dengan melalui logika Anda, itu masuk akal :)
codingbear
Anda dapat mengganti dua baris terakhir dari fungsi dengan "return sum (count_combs (...) for ...)" - dengan cara itu daftar tidak terwujud sama sekali. :)
Nick Johnson
Terima kasih atas tipnya. Saya selalu tertarik pada cara memperketat kode.
leif
2
Seperti dibahas dalam pertanyaan lain , kode ini akan memberikan keluaran yang salah jika daftar denominationstidak memiliki 1nilai terakhir. Anda dapat menambahkan sejumlah kecil kode ke ifblok terdalam untuk memperbaikinya (seperti yang saya jelaskan dalam jawaban saya untuk pertanyaan lain).
Blckknght
12

Fungsi skala:

def countChange(money: Int, coins: List[Int]): Int = {

def loop(money: Int, lcoins: List[Int], count: Int): Int = {
  // if there are no more coins or if we run out of money ... return 0 
  if ( lcoins.isEmpty || money < 0) 0
  else{
    if (money == 0 ) count + 1   
/* if the recursive subtraction leads to 0 money left - a prefect division hence return count +1 */
    else
/* keep iterating ... sum over money and the rest of the coins and money - the first item and the full set of coins left*/
      loop(money, lcoins.tail,count) + loop(money - lcoins.head,lcoins, count)
  }
}

val x = loop(money, coins, 0)
Console println x
x
}
jayaram S
sumber
Terima kasih! Ini adalah awal yang baik. Tapi, saya pikir ini gagal ketika "uang" mulai menjadi 0 :).
aqn
10

Berikut adalah beberapa kode C ++ yang sangat mudah untuk menyelesaikan masalah yang meminta semua kombinasi untuk ditampilkan.

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

Tapi saya cukup penasaran tentang sub masalah hanya menghitung jumlah kombinasi. Saya menduga ada persamaan bentuk tertutup untuk itu.

George Phillips
sumber
9
Tentunya ini C, bukan C ++.
nikhil
1
@ George Phillips dapatkah kamu menjelaskan?
Mencoba
Saya pikir itu cukup mudah. Pada dasarnya, idenya adalah untuk mengulang semua kuartal (menggunakan 0,1,2 .. maks), dan kemudian mengulang melalui semua sen berdasarkan kuartal yang digunakan, dll ..
Peter Lee
4
Kelemahan dari solusi ini adalah: jika ada koin 50 sen, 100 sen, 500 sen, maka kita harus menggunakan loop 6 tingkat ...
Peter Lee
3
Ini sangat buruk, jika Anda memiliki denominasi dinamis atau Anda ingin menambahkan denominasi lain maka ini tidak akan berhasil.
shinzou
7

Sub masalah adalah masalah Pemrograman Dinamis yang khas.

/* Q: Given some dollar value in cents (e.g. 200 = 2 dollars, 1000 = 10 dollars),
      find the number of combinations of coins that make up the dollar value.
      There are only penny, nickel, dime, and quarter.
      (quarter = 25 cents, dime = 10 cents, nickel = 5 cents, penny = 1 cent) */
/* A:
Reference: http://andrew.neitsch.ca/publications/m496pres1.nb.pdf
f(n, k): number of ways of making change for n cents, using only the first
         k+1 types of coins.

          +- 0,                        n < 0 || k < 0
f(n, k) = |- 1,                        n == 0
          +- f(n, k-1) + f(n-C[k], k), else
 */

#include <iostream>
#include <vector>
using namespace std;

int C[] = {1, 5, 10, 25};

// Recursive: very slow, O(2^n)
int f(int n, int k)
{
    if (n < 0 || k < 0)
        return 0;

    if (n == 0)
        return 1;

    return f(n, k-1) + f(n-C[k], k); 
}

// Non-recursive: fast, but still O(nk)
int f_NonRec(int n, int k)
{
    vector<vector<int> > table(n+1, vector<int>(k+1, 1));

    for (int i = 0; i <= n; ++i)
    {
        for (int j = 0; j <= k; ++j)
        {
            if (i < 0 || j < 0) // Impossible, for illustration purpose
            {
                table[i][j] = 0;
            }
            else if (i == 0 || j == 0) // Very Important
            {
                table[i][j] = 1;
            }
            else
            {
                // The recursion. Be careful with the vector boundary
                table[i][j] = table[i][j-1] + 
                    (i < C[j] ? 0 : table[i-C[j]][j]);
            }
        }
    }

    return table[n][k];
}

int main()
{
    cout << f(100, 3) << ", " << f_NonRec(100, 3) << endl;
    cout << f(200, 3) << ", " << f_NonRec(200, 3) << endl;
    cout << f(1000, 3) << ", " << f_NonRec(1000, 3) << endl;

    return 0;
}
Peter Lee
sumber
Solusi dinamis Anda membutuhkan k dengan panjang C minus 1. agak membingungkan. Anda dapat mengubahnya dengan mudah untuk mendukung panjang sebenarnya dari C.
Idan
7

Kode menggunakan Java untuk menyelesaikan masalah ini dan juga berfungsi ... Metode ini mungkin bukan ide yang baik karena terlalu banyak loop, tetapi ini benar-benar cara yang lurus ke depan.

public class RepresentCents {

    public static int sum(int n) {

        int count = 0;
        for (int i = 0; i <= n / 25; i++) {
            for (int j = 0; j <= n / 10; j++) {
                for (int k = 0; k <= n / 5; k++) {
                    for (int l = 0; l <= n; l++) {
                        int v = i * 25 + j * 10 + k * 5 + l;
                        if (v == n) {
                            count++;
                        } else if (v > n) {
                            break;
                        }
                    }
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        System.out.println(sum(100));
    }
}
Zihan
sumber
7

Ini adalah pertanyaan yang sangat lama, tapi saya datang dengan solusi rekursif di java yang tampak lebih kecil dari yang lain, jadi begini -

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

Perbaikan:

  public static void printAllCents(int ind, int[] denom,int N,int[] vals){
        if(N==0){
            if(ind < denom.length) {
                for(int i=ind;i<denom.length;i++)
                    vals[i] = 0;
            }
            System.out.println(Arrays.toString(vals));
            return;
        }
        if(ind == (denom.length)) {
            vals[ind-1] = 0;
            return;             
        }

        int currdenom = denom[ind];
        for(int i=0;i<=(N/currdenom);i++){ 
                vals[ind] = i;
                printAllCents(ind+1,denom,N-i*currdenom,vals);
        }
     }
Rohit Pandey
sumber
6

Misalkan C (i, J) himpunan kombinasi pembuatan i sen menggunakan nilai dalam himpunan J.

Anda dapat mendefinisikan C seperti itu:

masukkan deskripsi gambar di sini

(pertama (J) dengan cara deterministik mengambil elemen dari suatu himpunan)

Ternyata fungsi yang cukup rekursif ... dan cukup efisien jika Anda menggunakan memoization;)

akappa
sumber
Ya, ini ("pemrograman dinamis", dalam arti tertentu) akan menjadi solusi yang optimal.
ShreevatsaR
Anda benar: ambil J sebagai daftar dan bukan sebagai satu set: lalu pertama (J) membawa Anda elemen pertama dan J \ first (J) memberikan Anda sisa daftar.
akappa
bentuk matematika apa ini?
Muhammad Umer
5

semi-hack untuk mengatasi masalah kombinasi unik - paksa urutan menurun:

$ denom = [1,5,10,25]
def all_combs (jumlah, terakhir) 
  mengembalikan 1 jika jumlah == 0
  return $ denoms.select {| d | h & le jumlah && d & le terakhir} .inject (0) {| total, denom |
           total + all_combs (jumlah-denom, denom)}
akhir

Ini akan berjalan lambat karena tidak akan dimo, tetapi Anda mengerti.

klochner.dll
sumber
4
# short and sweet with O(n) table memory    

#include <iostream>
#include <vector>

int count( std::vector<int> s, int n )
{
  std::vector<int> table(n+1,0);

  table[0] = 1;
  for ( auto& k : s )
    for(int j=k; j<=n; ++j)
      table[j] += table[j-k];

  return table[n];
}

int main()
{
  std::cout <<  count({25, 10, 5, 1}, 100) << std::endl;
  return 0;
}
bjackfly
sumber
3

Ini jawaban saya dengan Python. Itu tidak menggunakan rekursi:

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)

Contoh keluaran

    ...
    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
Menandai
sumber
3
var countChange = function (money,coins) {
  function countChangeSub(money,coins,n) {
    if(money==0) return 1;
    if(money<0 || coins.length ==n) return 0;
    return countChangeSub(money-coins[n],coins,n) + countChangeSub(money,coins,n+1);
  }
  return countChangeSub(money,coins,0);
}
jasonhao
sumber
2

Keduanya: mengulangi semua denominasi dari tinggi ke rendah, ambil salah satu denominasi, kurangi dari total yang diminta, lalu ulangi sisa (membatasi denominasi yang tersedia menjadi sama atau lebih rendah ke nilai iterasi saat ini.)

djna
sumber
2

Jika sistem mata uang mengizinkannya, algoritme rakus sederhana yang mengambil sebanyak mungkin setiap koin, dimulai dengan mata uang dengan nilai tertinggi.

Jika tidak, pemrograman dinamis diperlukan untuk menemukan solusi optimal dengan cepat karena masalah ini pada dasarnya adalah masalah ransel .

Misalnya, jika sistem mata uang memiliki koin {13, 8, 1}:, solusi serakah akan membuat perubahan untuk 24 as {13, 8, 1, 1, 1}, tetapi solusi optimal yang sebenarnya adalah{8, 8, 8}

Sunting: Saya pikir kami membuat perubahan secara optimal, tidak mencantumkan semua cara untuk membuat perubahan untuk satu dolar. Wawancara saya baru-baru ini menanyakan bagaimana membuat perubahan jadi saya melompat ke depan sebelum menyelesaikan untuk membaca pertanyaan.

Ben S
sumber
masalahnya belum tentu untuk satu dolar - bisa 2 atau 23, jadi solusi Anda masih satu-satunya yang benar.
Neil G
2

Saya tahu ini pertanyaan yang sangat lama. Saya sedang mencari jawaban yang tepat dan tidak dapat menemukan apa pun yang sederhana dan memuaskan. Butuh beberapa waktu bagi saya tetapi bisa mencatat sesuatu.

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

Ini adalah solusi javascript dan menggunakan rekursi.

varun
sumber
Solusi ini hanya menemukan satu denominasi. Pertanyaannya adalah menemukan "semua" denominasi.
heinob
2

Dalam bahasa Pemrograman Scala saya akan melakukannya seperti ini:

 def countChange(money: Int, coins: List[Int]): Int = {

       money match {
           case 0 => 1
           case x if x < 0 => 0
           case x if x >= 1 && coins.isEmpty => 0
           case _ => countChange(money, coins.tail) + countChange(money - coins.head, coins)

       }

  }
MrOnyancha
sumber
2

Ini adalah algoritme rekursif sederhana yang mengambil satu tagihan, lalu mengambil tagihan yang lebih kecil secara rekursif hingga mencapai jumlah tersebut, lalu mengambil tagihan lain dengan denominasi yang sama, dan berulang lagi. Lihat contoh keluaran di bawah untuk ilustrasi.

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

Mencetak yang berikut ini:

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
pengguna7431997
sumber
1

Duh, aku merasa bodoh sekarang. Di bawah ini ada solusi yang terlalu rumit, yang akan saya pertahankan karena itu adalah solusi. Solusi sederhana adalah ini:

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

Inilah solusi lainnya. Solusi ini didasarkan pada pengamatan bahwa setiap koin adalah kelipatan dari yang lain, sehingga dapat direpresentasikan dalam bentuk koin.

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

Jadi, untuk 37 koin, misalnya:

scala> println(allCombinations(37))
0 quarter 0 dimes 0 nickels 37 pennies
0 quarter 0 dimes 1 nickel 32 pennies
0 quarter 0 dimes 2 nickels 27 pennies
0 quarter 0 dimes 3 nickels 22 pennies
0 quarter 0 dimes 4 nickels 17 pennies
0 quarter 0 dimes 5 nickels 12 pennies
0 quarter 0 dimes 6 nickels 7 pennies
0 quarter 0 dimes 7 nickels 2 pennies
0 quarter 1 dime 0 nickels 27 pennies
0 quarter 1 dime 1 nickel 22 pennies
0 quarter 1 dime 2 nickels 17 pennies
0 quarter 1 dime 3 nickels 12 pennies
0 quarter 1 dime 4 nickels 7 pennies
0 quarter 1 dime 5 nickels 2 pennies
0 quarter 2 dimes 0 nickels 17 pennies
0 quarter 2 dimes 1 nickel 12 pennies
0 quarter 2 dimes 2 nickels 7 pennies
0 quarter 2 dimes 3 nickels 2 pennies
0 quarter 3 dimes 0 nickels 7 pennies
0 quarter 3 dimes 1 nickel 2 pennies
1 quarter 0 dimes 0 nickels 12 pennies
1 quarter 0 dimes 1 nickel 7 pennies
1 quarter 0 dimes 2 nickels 2 pennies
1 quarter 1 dime 0 nickels 2 pennies
Daniel C. Sobral
sumber
1

Entri blog saya ini memecahkan masalah seperti ransel untuk tokoh-tokoh dari komik XKCD . Perubahan sederhana pada itemsdict dan exactcostnilainya akan menghasilkan semua solusi untuk masalah Anda juga.

Jika masalahnya adalah menemukan perubahan yang menggunakan biaya paling sedikit, maka algoritme rakus naif yang menggunakan koin bernilai tertinggi mungkin gagal untuk beberapa kombinasi koin dan jumlah target. Misalnya jika ada koin dengan nilai 1, 3, dan 4; dan jumlah targetnya adalah 6 maka algoritme rakus mungkin menyarankan tiga koin dengan nilai 4, 1, dan 1 jika mudah untuk melihat bahwa Anda dapat menggunakan dua koin yang masing-masing bernilai 3.

  • Padi.
Paddy3118
sumber
1
public class Coins {

static int ac = 421;
static int bc = 311;
static int cc = 11;

static int target = 4000;

public static void main(String[] args) {


    method2();
}

  public static void method2(){
    //running time n^2

    int da = target/ac;
    int db = target/bc;     

    for(int i=0;i<=da;i++){         
        for(int j=0;j<=db;j++){             
            int rem = target-(i*ac+j*bc);               
            if(rem < 0){                    
                break;                  
            }else{                  
                if(rem%cc==0){                  
                    System.out.format("\n%d, %d, %d ---- %d + %d + %d = %d \n", i, j, rem/cc, i*ac, j*bc, (rem/cc)*cc, target);                     
                }                   
            }                   
        }           
    }       
}
 }
Amit Patil
sumber
1

Saya menemukan potongan kode yang rapi ini di buku "Python Untuk Analisis Data" oleh O'reily. Ini menggunakan implementasi malas dan perbandingan int dan saya kira itu dapat dimodifikasi untuk denominasi lain menggunakan desimal. Beri tahu saya cara kerjanya untuk Anda!

def make_change(amount, coins=[1, 5, 10, 25], hand=None):
 hand = [] if hand is None else hand
 if amount == 0:
 yield hand
 for coin in coins:
 # ensures we don't give too much change, and combinations are unique
 if coin > amount or (len(hand) > 0 and hand[-1] < coin):
 continue
 for result in make_change(amount - coin, coins=coins,
 hand=hand + [coin]):
 yield result

Suhas
sumber
1

Inilah perbaikan dari jawaban Zihan. Banyak putaran yang tidak perlu terjadi jika denominasi hanya 1 sen.

Ini intuitif dan non-rekursif.

    public static int Ways2PayNCents(int n)
    {
        int numberOfWays=0;
        int cent, nickel, dime, quarter;
        for (quarter = 0; quarter <= n/25; quarter++)
        {
            for (dime = 0; dime <= n/10; dime++)
            {
                for (nickel = 0; nickel <= n/5; nickel++)
                {
                    cent = n - (quarter * 25 + dime * 10 + nickel * 5);
                    if (cent >= 0)
                    {
                        numberOfWays += 1;
                        Console.WriteLine("{0},{1},{2},{3}", quarter, dime, nickel, cent);
                    }                   
                }
            }
        }
        return numberOfWays;            
    }
Aerin
sumber
Anda tidak dapat menggeneralisasi solusi ini, jadi misalnya elemen baru muncul dalam hal ini Anda harus menambahkan perulangan for
Sumit Kumar Saha
1

Solusi java langsung:

public static void main(String[] args) 
{    
    int[] denoms = {4,2,3,1};
    int[] vals = new int[denoms.length];
    int target = 6;
    printCombinations(0, denoms, target, vals);
}


public static void printCombinations(int index, int[] denom,int target, int[] vals)
{
  if(target==0)
  {
    System.out.println(Arrays.toString(vals));
    return;
  }
  if(index == denom.length) return;   
  int currDenom = denom[index];
  for(int i = 0; i*currDenom <= target;i++)
  {
    vals[index] = i;
    printCombinations(index+1, denom, target - i*currDenom, vals);
    vals[index] = 0;
  }
}
GR44
sumber
1
/*
* make a list of all distinct sets of coins of from the set of coins to
* sum up to the given target amount.
* Here the input set of coins is assumed yo be {1, 2, 4}, this set MUST
* have the coins sorted in ascending order.
* Outline of the algorithm:
* 
* Keep track of what the current coin is, say ccn; current number of coins
* in the partial solution, say k; current sum, say sum, obtained by adding
* ccn; sum sofar, say accsum:
*  1) Use ccn as long as it can be added without exceeding the target
*     a) if current sum equals target, add cc to solution coin set, increase
*     coin coin in the solution by 1, and print it and return
*     b) if current sum exceeds target, ccn can't be in the solution, so
*        return
*     c) if neither of the above, add current coin to partial solution,
*        increase k by 1 (number of coins in partial solution), and recuse
*  2) When current denomination can no longer be used, start using the
*     next higher denomination coins, just like in (1)
*  3) When all denominations have been used, we are done
*/

#include <iostream>
#include <cstdlib>

using namespace std;

// int num_calls = 0;
// int num_ways = 0;

void print(const int coins[], int n);

void combine_coins(
                   const int denoms[], // coins sorted in ascending order
                   int n,              // number of denominations
                   int target,         // target sum
                   int accsum,         // accumulated sum
                   int coins[],        // solution set, MUST equal
                                       // target / lowest denom coin
                   int k               // number of coins in coins[]
                  )
{

    int  ccn;   // current coin
    int  sum;   // current sum

    // ++num_calls;

    for (int i = 0; i < n; ++i) {
        /*
         * skip coins of lesser denomination: This is to be efficient
         * and also avoid generating duplicate sequences. What we need
         * is combinations and without this check we will generate
         * permutations.
         */
        if (k > 0 && denoms[i] < coins[k - 1])
            continue;   // skip coins of lesser denomination

        ccn = denoms[i];

        if ((sum = accsum + ccn) > target)
            return;     // no point trying higher denominations now


        if (sum == target) {
            // found yet another solution
            coins[k] = ccn;
            print(coins, k + 1);
            // ++num_ways;
            return;
        }

        coins[k] = ccn;
        combine_coins(denoms, n, target, sum, coins, k + 1);
    }
}

void print(const int coins[], int n)
{
    int s = 0;
    for (int i = 0; i < n; ++i) {
        cout << coins[i] << " ";
        s += coins[i];
    }
    cout << "\t = \t" << s << "\n";

}

int main(int argc, const char *argv[])
{

    int denoms[] = {1, 2, 4};
    int dsize = sizeof(denoms) / sizeof(denoms[0]);
    int target;

    if (argv[1])
        target = atoi(argv[1]);
    else
        target = 8;

    int *coins = new int[target];


    combine_coins(denoms, dsize, target, 0, coins, 0);

    // cout << "num calls = " << num_calls << ", num ways = " << num_ways << "\n";

    return 0;
}
rpk
sumber
1

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

Gunakan seperti ini:

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

Ini mencetak:

25; 25; 25; 25
10; 10; 10; 10; 10; 25; 25
10; 10; 10; 10; 10; 10; 10; 10; 10; 10
5; 10; 10; 25; 25; 25
5; 10; 10; 10; 10; 10; 10; 10; 25
5; 5; 10; 10; 10; 10; 25; 25
5; 5; 10; 10; 10; 10; 10; 10; 10; 10; 10
5; 5; 5; 10; 25; 25; 25
5; 5; 5; 10; 10; 10; 10; 10; 10; 25
5; 5; 5; 5; 10; 10; 10; 25; 25
5; 5; 5; 5; 10; 10; 10; 10; 10; 10; 10; 10
5; 5; 5; 5; 5; 25; 25; 25
5; 5; 5; 5; 5; 10; 10; 10; 10; 10; 25
5; 5; 5; 5; 5; 5; 10; 10; 25; 25
5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 10; 10; 10
5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 25
5; 5; 5; 5; 5; 5; 5; 5; 10; 25; 25
5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 10; 10
5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 25
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 25; 25
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10; 10
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 25
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10; 10
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 25
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10; 10
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 25
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10; 10
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 10
5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5; 5
shinzou
sumber
Outputnya cantik
Terima kasih
1

Di bawah ini adalah program python untuk menemukan semua kombinasi uang. Ini adalah solusi pemrograman dinamis dengan waktu order (n). Uang 1,5,10,25

Kami melintasi dari uang baris 1 ke uang baris 25 (4 baris). Uang baris 1 berisi hitungan jika kita hanya memperhitungkan uang 1 dalam menghitung jumlah kombinasi. Uang baris 5 menghasilkan setiap kolom dengan menghitung uang baris r untuk uang akhir yang sama ditambah 5 hitungan sebelumnya di barisnya sendiri (posisi saat ini dikurangi 5). Uang baris 10 menggunakan uang baris 5, yang berisi hitungan untuk 1,5 dan ditambahkan dalam hitungan 10 sebelumnya (posisi saat ini dikurangi 10). Uang baris 25 menggunakan uang baris 10 yang berisi hitungan uang baris 1,5,10 ditambah 25 hitungan sebelumnya.

Misalnya, angka [1] [12] = angka [0] [12] + angka [1] [7] (7 = 12-5) yang menghasilkan 3 = 1 + 2; angka [3] [12] = angka [2] [12] + angka [3] [9] (-13 = 12-25) yang menghasilkan 4 = 0 + 4, karena -13 kurang dari 0.

def cntMoney(num):
    mSz = len(money)
    numbers = [[0]*(1+num) for _ in range(mSz)]
    for mI in range(mSz): numbers[mI][0] = 1
    for mI,m in enumerate(money):
        for i in range(1,num+1):
            numbers[mI][i] = numbers[mI][i-m] if i >= m else 0
            if mI != 0: numbers[mI][i] += numbers[mI-1][i]
        print('m,numbers',m,numbers[mI])
    return numbers[mSz-1][num]

money = [1,5,10,25]
    num = 12
    print('money,combinations',num,cntMoney(num))

output:    
('m,numbers', 1, [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1])
('m,numbers', 5, [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3])
('m,numbers', 10, [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 4])
('m,numbers', 25, [1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 4])
('money,combinations', 12, 4)
edW
sumber
0

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

}

}

Beruang
sumber
0

Solusi java di bawah ini yang akan mencetak kombinasi yang berbeda juga. Mudah dimengerti. Ide adalah

untuk jumlah 5

Solusinya adalah

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

Jika jumlah yang tersisa di setiap loop lebih kecil dari denominasi yaitu jika jumlah tersisa 1 lebih kecil dari 2, maka putus saja loop

Kode lengkapnya di bawah ini

Harap perbaiki saya jika ada kesalahan

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

}

FatherMathew
sumber
0

Berikut adalah solusi berbasis python yang menggunakan rekursi serta memoization yang menghasilkan kompleksitas O (mxn)

    def get_combinations_dynamic(self, amount, coins, memo):
    end_index = len(coins) - 1
    memo_key = str(amount)+'->'+str(coins)
    if memo_key in memo:
        return memo[memo_key]
    remaining_amount = amount
    if amount < 0:
        return []
    if amount == 0:
        return [[]]
    combinations = []
    if len(coins) <= 1:
        if amount % coins[0] == 0:
            combination = []
            for i in range(amount // coins[0]):
                combination.append(coins[0])
            list.sort(combination)
            if combination not in combinations:
                combinations.append(combination)
    else:
        k = 0
        while remaining_amount >= 0:
            sub_combinations = self.get_combinations_dynamic(remaining_amount, coins[:end_index], memo)
            for combination in sub_combinations:
                temp = combination[:]
                for i in range(k):
                    temp.append(coins[end_index])
                list.sort(temp)
                if temp not in combinations:
                    combinations.append(temp)
            k += 1
            remaining_amount -= coins[end_index]
    memo[memo_key] = combinations
    return combinations
lalatnayak
sumber
Oke saya ragu di atas memiliki waktu berjalan polinomial. Tidak yakin apakah kami dapat memiliki waktu proses polinomial sama sekali. Tapi apa yang saya amati adalah yang di atas berjalan lebih cepat daripada versi non-memoized dalam banyak kasus. Saya akan melanjutkan penelitian mengapa
lalatnayak