달러 가치가 주어 졌을 때 모든 코인 조합을 찾는 방법


114

몇 달 전에 인터뷰 준비를 위해 작성한 코드를 발견했습니다.

내가 가진 의견에 따르면이 문제를 해결하려고했습니다.

센트 단위의 달러 가치 (예 : 200 = 2 달러, 1000 = 10 달러)가 주어지면 달러 가치를 구성하는 모든 동전 조합을 찾습니다. 페니 (1 ¢), 니켈 (5 ¢), 다임 (10 ¢) 및 쿼터 (25 ¢) 만 허용됩니다.

예를 들어 100이 주어진 경우 답은 다음과 같아야합니다.

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

나는 이것이 반복적이고 재귀적인 방법으로 해결 될 수 있다고 믿습니다. 내 재귀 솔루션은 매우 버그가 많으며 다른 사람들이이 문제를 어떻게 해결할지 궁금합니다. 이 문제의 어려운 부분은 가능한 한 효율적으로 만드는 것이 었습니다.


6
@akappa : 페니 = 1 센트; 니켈 = 5 센트; 한푼 = 10 센트; 분기 = 25 센트 :)
codingbear

@John T : 코드 골프? 나는 그 용어에 대해 들어 본 적이 없다! 어쨌든, 나는 SO 커뮤니티는 어떤 문제를 해결할 수 있기 때문에, 몇 가지 흥미로운 답변을 뵙기를 희망합니다
codingbear

나는 또한 집에 도착하면 내 답변을 게시하려고 노력할 것입니다. 아직 직장에 있고 너무 많은 시간을 보내지 않아야합니다.
codingbear

1
@blee 코드 골프는 선택한 프로그래밍 언어로 가능한 최소한의 문자로 문제를 해결하는 것을 말합니다. 다음은이 웹 사이트에서 수행 된 작업입니다. stackoverflow.com/search?q=code+golf
John T

답변:


54

나는 이것을 오래 전에 한 번 살펴 보았고 그것에 대한 나의 작은 글을 읽을 수 있습니다 . 다음은 Mathematica 소스 입니다.

생성 함수를 사용하여 문제에 대한 폐쇄 형 상수 시간 솔루션을 얻을 수 있습니다. Graham, Knuth 및 Patashnik의 Concrete Mathematics 는이를위한 책이며 문제에 대한 상당히 광범위한 논의를 포함합니다. 기본적으로 n 번째 계수가 n 달러에 대한 변경 방법의 수인 다항식을 정의합니다 .

이 글의 4 ~ 5 페이지는 Mathematica (또는 기타 편리한 컴퓨터 대수 시스템)를 사용하여 3 줄의 코드로 몇 초 만에 10 ^ 10 ^ 6 달러에 대한 답을 계산하는 방법을 보여줍니다.

(그리고 이것은 75Mhz 펜티엄에서 몇 초 정도 전에 충분히 오래되었습니다 ...)


16
좋은 대답이지만 사소한 문제 : (1) 이것은 방법 의 를 제공하는 반면 어떤 이유로 질문은 모든 방법의 실제 세트를 요구합니다. 물론 출력 자체에 초 다항식으로 많은 항목이 있으므로 다항식 시간에서 집합을 찾을 수있는 방법은 없습니다. (2) 생성 함수가 "폐쇄 형"인지 여부는 논쟁의 여지가 있습니다 (Herbert Wilf의 멋진 책 Generatingfunctionology : math 참조). upenn.edu/~wilf/DownldGF.html ) 그리고 (1 + √5) ^ n과 같은 표현을 의미한다면, 계산하는 데 상수 시간이 아니라 Ω (log n) 시간이 걸립니다.
ShreevatsaR

동적 프로그래밍에 대한 간단한 소개. 또한 시퀀스 문제가있는 사람은 누구나 생성 기능 을 읽을 것을 권장 합니다.
패닉 대령

덕분에 많은 앤드류 있도록 ...이 설명은 아래의 스칼라 함수를 게시 ... 너무 많은 나를 도와 ... 할 몇 가지 일 필요가
자야 람 S

1
처음에 질문은 "... 1, 10, 25, 50, 100 센트 동전을 사용 하는가?"라는 질문을하기 때문에 약간의 수정이 필요하다고 생각합니다. 그러나 쓰기는 세트 afbut 의 도메인으로 정의합니다 a = {1,5,10,25,50,100}. 센트 동전 목록에 5가 있어야합니다. 그렇지 않으면 쓰기가 환상적이었습니다. 감사합니다!
rbrtl

@rbrtl 와우, 당신 말이 맞아요, 알아 주셔서 감사합니다! 나는 그것을 업데이트 할 것입니다 ...
andrewdotn

42

참고 : 이것은 방법의 수만 표시합니다.

스칼라 함수 :

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을 변경하는 방법이 실제로 있습니까? 그렇게 할 방법이 없다고 생각합니다.
Luke

2
이는 다항식 솔루션의 수에서 비롯됩니다 n1 * coins(0) + n2 * coins(1) + ... + nN * coins(N-1) = money. 이처럼 money=0coins=List(1,2,5,10)조합의 수는 (n1, n2, n3, n4)1이고 솔루션입니다 (0, 0, 0, 0).
Kyr

3
이 구현이 작동하는 이유에 대해 머리를 감쌀 수는 없습니다. 누군가 나에게 알고리즘을 설명 할 수 있습니까?
아드 르메르

3
이것은 물론 코스 라 스칼라 코스의 운동 1의 문제 3에 대한 정확한 답입니다.
저스틴 표준

나는 경우, 있다고 생각 money == 0하지만 coins.isEmpty, 그것은 sol'n으로 간주해서는 안된다. 따라서 coins.isEmpty || money < 0조건이 먼저 확인되면 algo가 더 잘 제공 될 수 있습니다 .
juanchito

26

재귀 솔루션을 선호합니다. 가장 작은 금액이 남은 통화 금액을 균등하게 나눌 수 있다면 교파 목록이 있습니다.

기본적으로 가장 큰 교단에서 가장 작은 교단으로 이동합니다.
재귀 적으로

  1. 채울 현재 총액과 가장 큰 교단 (1 개 이상 남음)이 있습니다. 교단이 1 개만 남아있는 경우 총액을 채우는 방법은 하나뿐입니다. k * cur denomination <= total이되도록 0에서 k 개의 현재 교파 사본을 사용할 수 있습니다.
  2. 0에서 k까지의 경우 수정 된 총액과 새로운 가장 큰 액면가로 함수를 호출하십시오.
  3. 0에서 k까지의 결과를 더합니다. 그것이 현재의 교단에서 총액을 채울 수있는 방법은 많습니다. 이 번호를 반환하십시오.

여기에 200 센트에 대한 귀하의 언급 된 문제의 파이썬 버전이 있습니다. 1463 가지 방법이 있습니다. 이 버전은 모든 조합과 최종 개수 합계를 인쇄합니다.

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

이 그것을 실행하지,하지만 당신의 논리를 통과함으로써, :) 의미가 있습니다
codingbear

함수의 마지막 두 줄을 "return sum (count_combs (...) for ...)"으로 바꿀 수 있습니다. 이렇게하면 목록이 전혀 구체화되지 않습니다. :)
Nick Johnson

팁 고마워. 저는 항상 코드를 강화하는 방법에 관심이 있습니다.
leif

2
다른 질문 에서 논의했듯이 목록에 마지막 값 denominations이 없으면 이 코드는 잘못된 출력을 제공 1합니다. 가장 안쪽 if블록에 소량의 코드를 추가 하여 수정할 수 있습니다 (다른 질문에 대한 답변에서 설명했듯이).
Blckknght 2015

12

스칼라 함수 :

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
}

감사! 이것은 좋은 시작입니다. 그러나 "money"가 0으로 시작하면 실패한다고 생각합니다. :).
aqn

10

여기에 모든 조합이 표시되도록 요구했던 문제를 해결하기위한 절대적으로 간단한 C ++ 코드가 있습니다.

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

그러나 나는 단지 조합의 수를 계산하는 하위 문제에 대해 상당히 흥미 롭습니다. 나는 그것에 대한 폐쇄 형 방정식이 있다고 생각합니다.


9
확실히 이것은 C ++가 아니라 C입니다.
nikhil 2012-07-09

1
@George Phillips는 설명 할 수 있습니까?
2013 년

꽤 간단하다고 생각합니다. 기본적으로, 아이디어는 반복 모든 (0,1,2를 사용하여 .. 최대) 분기, 다음으로 반복 사용되는 분기를 기반으로 모든 다임 통해, 등
피터 리

4
이 솔루션의 단점은 : 50 센트, 100 센트, 500 센트 동전이있는 경우, 우리는 6 레벨의 루프를 사용해야합니다 ...
피터 리

3
이것은 매우 나쁘다. 당신이 동적 인 교단을 가지고 있거나 다른 교단을 추가하고 싶다면 이것은 작동하지 않을 것이다.
shinzou

7

하위 문제는 일반적인 동적 프로그래밍 문제입니다.

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

동적 솔루션은 k가 C에서 1을 뺀 길이 여야합니다. 약간 혼란 스럽습니다. 당신은 C.의 실제 길이를 지원하기 위해 쉽게 변경할 수 있습니다
Idan

7

코드는이 문제를 해결하기 위해 Java를 사용하고 있으며 또한 작동합니다 ...이 방법은 루프가 너무 많기 때문에 좋은 생각이 아닐 수 있지만 실제로는 간단한 방법입니다.

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

이것은 정말 오래된 질문이지만 다른 모든 것보다 작아 보이는 자바 재귀 솔루션을 생각해 냈습니다.

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

개량:

  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

집합 J의 값을 사용하여 i 센트를 만드는 조합 집합을 C (i, J)로 지정합니다.

C를 다음과 같이 정의 할 수 있습니다.

여기에 이미지 설명 입력

(first (J)는 세트의 요소를 결정적인 방식으로 취 합니다)

그것은 꽤 재귀적인 함수로 밝혀졌습니다 ... 그리고 당신이 메모를 사용한다면 합리적으로 효율적입니다;)


예, 이것은 ( "동적 프로그래밍", 어떤 의미에서) 최적의 솔루션이 될 것입니다.
ShreevatsaR

당신이 옳습니다 : J를 세트가 아닌 목록으로 취하십시오. 그러면 first (J)가 첫 번째 요소를 가져오고 J \ first (J)가 나머지 목록을 제공합니다.
akappa

이것은 어떤 형태의 수학입니까?
Muhammad Umer

5

고유 한 조합 문제를 해결하기위한 세미 해킹-강제 내림차순 :

$ denoms = [1,5,10,25]
def all_combs (sum, last) 
  sum == 0이면 1 반환
  return $ denoms.select {| d | d & le sum && d & le last} .inject (0) {| total, denom |
           total + all_combs (sum-denom, denom)}
종료

이것은 메모되지 않기 때문에 느리게 실행되지만 아이디어를 얻습니다.


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의 내 대답입니다. 재귀를 사용하지 않습니다.

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)

예제 출력

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

둘 다 : 높은 곳에서 낮은 곳으로 모든 교단을 반복하고, 교단 중 하나를 취하고, 요청 된 총액에서 뺀 다음 나머지 부분에서 반복합니다 (사용 가능한 교단이 현재 반복 값과 같거나 낮도록 제한).


2

통화 시스템이 허용하는 경우, 가장 높은 가치의 통화부터 시작하여 가능한 한 많은 각 동전을 사용 하는 단순한 탐욕스러운 알고리즘 입니다.

그렇지 않으면이 문제는 본질적으로 배낭 문제 이기 때문에 최적의 솔루션을 빠르게 찾기 위해 동적 프로그래밍이 필요합니다 .

예를 들어, 통화 시스템에 동전이있는 {13, 8, 1}경우 욕심 많은 솔루션은 24를로 변경 {13, 8, 1, 1, 1}하지만 진정한 최적 솔루션은 다음과 같습니다.{8, 8, 8}

편집 : 저는 우리가 최적으로 변경하고 있다고 생각했습니다. 단 1 달러로 변경하는 모든 방법을 나열하지 않았습니다. 최근 인터뷰에서 변경 방법을 물었 기 때문에 질문을 읽기 전에 한 발 더 앞서 나갔습니다.


문제가 반드시 1 달러에 해당하는 것은 아닙니다. 2 또는 23 달러가 될 수 있으므로 귀하의 솔루션이 여전히 유일합니다.
Neil G

2

나는 이것이 매우 오래된 질문이라는 것을 알고 있습니다. 정답을 찾다가 간단하고 만족스러운 것을 찾지 못했습니다. 시간이 좀 걸렸지 만 적어 둘 수있었습니다.

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

이것은 자바 스크립트 솔루션이며 재귀를 사용합니다.


이 솔루션은 하나의 교단 만 찾습니다. 문제는 "모든"교단을 찾는 것이 었습니다.
heinob

2

스칼라 프로그래밍 언어에서는 다음과 같이 할 것입니다.

 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

이것은 지폐를 가져 와서 합계에 도달 할 때까지 더 작은 지폐를 재귀 적으로 가져 와서 동일한 액면가의 다른 지폐를 가져와 다시 반복하는 간단한 재귀 알고리즘입니다. 설명은 아래 샘플 출력을 참조하십시오.

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

다음을 인쇄합니다.

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

야, 지금 바보 같아. 아래에는 지나치게 복잡한 솔루션 있습니다. 결국 솔루션 이기 때문에 보존하겠습니다 . 간단한 해결책은 다음과 같습니다.

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

여기에 다른 해결책이 있습니다. 이 솔루션은 각 코인이 다른 코인의 배수라는 관찰을 기반으로하므로 코인으로 표현할 수 있습니다.

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

예를 들어, 37 개 코인의 경우 :

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

이 블로그 항목은 XKCD 만화 의 인물에 대한 문제와 같은 배낭 문제를 해결합니다 . itemsdict와 exactcost값 을 간단히 변경하면 문제에 대한 모든 솔루션이 생성됩니다.

문제가 가장 적은 비용을 사용하는 변경 사항을 찾는 것이라면, 가장 높은 가치의 코인을 많이 사용한 순진한 탐욕스러운 알고리즘은 일부 코인과 목표 금액의 조합에 대해 실패 할 수 있습니다. 예를 들어 값이 1, 3, 4 인 동전이있는 경우; 목표 금액이 6이면 욕심 많은 알고리즘이 가치 3의 동전 2 개를 사용할 수 있다는 것을 쉽게 알 수있을 때 가치 4, 1, 1의 동전 3 개를 제안 할 수 있습니다.

  • 아일랜드 사람.

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의 "Python For Data Analysis"책에서이 깔끔한 코드를 찾았습니다. 그것은 게으른 구현과 int 비교를 사용하며 소수를 사용하여 다른 종파에 대해 수정할 수 있다고 가정합니다. 어떻게 작동하는지 알려주세요!

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

이것은 Zihan의 대답의 개선입니다. 교단이 1 센트 일 때 불필요한 루프가 많이 발생합니다.

직관적이고 비재 귀적입니다.

    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는 당신이 루프 다른를 추가 할 필요가 있으므로, 예를 들어 새로운 요소가이 경우에 제공,이 솔루션을 일반화 할 수없는
수밋 쿠마 사하

1

간단한 자바 솔루션 :

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

다음은 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));
    }

다음과 같이 사용하십시오.

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

다음을 인쇄합니다.

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

출력은 예쁘다
감사합니다

1

아래는 모든 돈 조합을 찾는 파이썬 프로그램입니다. 이것은 order (n) 시간이있는 동적 프로그래밍 솔루션입니다. 돈은 1,5,10,25

행 돈 1에서 행 돈 25 (4 행)로 이동합니다. 조합 수를 계산할 때 돈 1 만 고려하는 경우 행 돈 1에는 개수가 포함됩니다. 행 돈 5는 동일한 최종 돈에 대해 행 돈 r의 수와 자체 행의 이전 5 수 (현재 위치에서 5를 뺀 값)를 더하여 각 열을 생성합니다. 행 돈 10은 행 돈 5를 사용하는데, 여기에는 1,5에 대한 개수가 포함되어 있고 이전 10 개수에 추가됩니다 (현재 위치에서 10을 뺀 값). 행 돈 25는 행 돈 1,5,10에 이전 25 개 수를 더한 수를 포함하는 행 돈 10을 사용합니다.

예를 들어, numbers [1] [12] = numbers [0] [12] + numbers [1] [7] (7 = 12-5), 결과는 3 = 1 + 2; 숫자 [3] [12] = 숫자 [2] [12] + 숫자 [3] [9] (-13 = 12-25), 결과는 4 = 0 + 4입니다. -13은 0보다 작기 때문입니다.

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

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

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

0

자바 솔루션

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

아래의 자바 솔루션은 다른 조합도 인쇄합니다. 이해하기 쉬운. 아이디어는

합계 5

해결책은

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

각 루프의 나머지 합계가 교단보다 작 으면 즉, 나머지 합계 1이 2보다 작 으면 루프를 끊습니다.

아래 전체 코드

실수가 있으면 수정 해주세요.

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

다음은 재귀와 메모를 사용하는 파이썬 기반 솔루션으로 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에게
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.