Как найти все комбинации монет, когда дано какое-то долларовое значение - PullRequest
109 голосов
/ 10 июля 2009

Я нашел кусок кода, который писал для подготовки к собеседованию несколько месяцев назад.

Согласно моему комментарию, я пытался решить эту проблему:

Учитывая некоторую долларовую стоимость в центах (например, 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.

Я считаю, что это можно решить как итеративным, так и рекурсивным способом. Мое рекурсивное решение довольно глючное, и мне было интересно, как другие люди решат эту проблему. Сложной частью этой проблемы было сделать ее максимально эффективной.

Ответы [ 34 ]

50 голосов
/ 10 июля 2009

Я изучал это однажды давным-давно, и вы можете прочитать мою небольшую статью об этом . Вот источник Mathematica .

Используя генерирующие функции, вы можете получить решение проблемы в постоянном времени в замкнутой форме. Книга Грэма, Кнута и Паташника Конкретная математика является книгой для этого и содержит довольно обширное обсуждение проблемы. По сути, вы определяете многочлен, где n th коэффициент - это количество способов внесения изменений за n долларов.

На страницах 4-5 статьи показано, как вы можете использовать Mathematica (или любую другую удобную систему компьютерной алгебры), чтобы вычислить ответ за 10 ^ 10 ^ 6 долларов за пару секунд в трех строках кода.

(И это было достаточно давно, это пара секунд на 75 МГц Pentium ...)

43 голосов
/ 18 сентября 2013

Примечание : показывает только количество способов.

Функция Scala:

def countChange(money: Int, coins: List[Int]): Int =
  if (money == 0) 1
  else if (coins.isEmpty || money < 0) 0
  else countChange(money - coins.head, coins) + countChange(money, coins.tail)
24 голосов
/ 10 июля 2009

Я бы предпочел рекурсивное решение. У вас есть список наименований, если самый маленький из них может равномерно разделить оставшуюся сумму в валюте, это должно работать нормально.

По сути, вы переходите от самых больших к самым маленьким конфессиям.
Рекурсивный

  1. У вас есть текущая сумма для заполнения, а также самый большой номинал (с более чем 1 осталось). Если осталось только 1 номинал, есть только один способ пополнить итоговую сумму. Вы можете использовать от 0 до k копий вашего текущего номинала, так что k * cur denomination <= total. </li>
  2. Для значений от 0 до k вызовите функцию с измененным итогом и новым наибольшим номиналом.
  3. Суммируйте результаты от 0 до k. Вот сколько способов вы можете заполнить свою сумму, начиная с текущего номинала и далее. Верните это число.

Вот моя заявленная Python-версия вашей проблемы за 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:
            comb.append( (left, denominations[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)
12 голосов
/ 29 марта 2013

функция Scala:

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

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

val x = loop(money, coins, 0)
Console println x
x
}
10 голосов
/ 10 июля 2009

Вот некоторый абсолютно простой код 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;
}

Но я весьма заинтригован подзадачей, состоящей в простом подсчете количества комбинаций. Я подозреваю, что для него есть уравнение в замкнутой форме.

7 голосов
/ 28 августа 2013

Это действительно старый вопрос, но я придумал рекурсивное решение в Java, которое казалось меньше, чем все остальные, так что здесь -

 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);
        }
     }
7 голосов
/ 10 января 2014

Код использует 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 голосов
/ 29 июля 2013

Подзадача является типичной проблемой динамического программирования.

/* 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;
}
6 голосов
/ 10 июля 2009

Пусть C (i, J) множество комбинаций составления i центов, используя значения в наборе J.

Вы можете определить C следующим образом:

enter image description here

(первый (J) принимает детерминированным образом элемент набора)

Получается довольно рекурсивная функция ... и достаточно эффективная, если вы используете мемоизацию;)

5 голосов
/ 10 июля 2009

полукак для решения уникальной проблемы с комбинацией - принудительное убывание:

$denoms = [1,5,10,25]
def all_combs(sum,last) 
  return 1 if sum == 0
  return $denoms.select{|d| d &le sum && d &le last}.inject(0) {|total,denom|
           total+all_combs(sum-denom,denom)}
end

Это будет работать медленно, так как не будет запомнено, но вы поймете идею.

...