打印硬币找零算法的最佳解决方案

时间:2020-05-29 00:52:40

标签: java algorithm

给定值N,如果我们要为N cents进行找零,并且有无限数量的S = { S1, S2, .. , Sm}面值的硬币供应,那么进行找零的最佳方法是N cents

示例:

S = {2, 5, 10}
N = 6, then optimal solution is : 2, 2, 2

我有以下工作代码:

public static void main(String argv[]) {
        long n = 10L;
        int[] combo = new int[100];
        int[] amounts = { 2, 5, 10 };
        ways(n, amounts, combo, 0, 0, 0);
    }

    public static void ways(long n, int[] amounts, int[] combo, int startIndex, int sum, int index) {
        if (sum == n) {
            printArray(combo, index);
        }

        if (sum > n) {
            return;
        }

        for (int i = 0; i < amounts.length; i++) {
            sum = sum + amounts[i];
            combo[index] = amounts[i];
            ways(n, amounts, combo, startIndex, sum, index + 1);
            sum = sum - amounts[i];
        }
    }

    public static void printArray(int[] combo, int index) {
        for (int i = 0; i < index; i++) {
            System.out.print(combo[i] + " ");
        }
        System.out.println();
    }

输出:

2 2 2 2 2 
5 5 
10 

在这里,我只需要使用较少数量的硬币来实现最佳组合,因此在此示例代码中只有10

但是此代码使用递归方法,我对N的值是Long类型,所以当N的值增加时,我会得到stackoverflow错误。

我在这里采用的递归方法是不正确的,解决此问题的正确方法是什么?

更新

基于MBo答案,我尝试了以下程序,但无法获得正确的结果:

static void testcase() {
        // make int array A of size N+1
        int N = 6;
        int[] A = new int[N + 1];
        // make int array P of size N+1
        int[] P = new int[N + 1];
        // fill A[] with large value (len(S) + 1)
        int[] S = { 2, 5, 10 };
        int lengthOfS = S.length;
        for (int i = 0; i < A.length; i++) {
            A[i] = lengthOfS + 1;
        }

        A[0] = 0;

        for (int s : S) {// coin value
            for (int i = s; i <= N; i++) {
                if (A[i - s] < A[i] + 1) { // using this coin we can get better
                                            // result for sum i
                    A[i] = A[i - s] + 1;
                    P[i] = s; // write down coin for this sum
                }
            }
        }
        System.out.println(Arrays.toString(P)); // [0, 0, 2, 2, 2, 5, 2]
        System.out.println(A[N]);// 3
        int idx = N;
        for (int i = 0; i < A[N]; i++) {
            int result = idx - P[idx];
            System.out.println(result); // 4 2 0
            idx = result;
        }
    }

此代码显示:

[0, 0, 2, 2, 2, 5, 2]
3

4
2
0

如何修复此代码?

2 个答案:

答案 0 :(得分:3)

对于固定集S = {2, 5, 10},解决方案非常简单:

N=1,3的解决方案

如果N为奇数,则必须使用5-所以N=N-5

现在使用贪婪方法:获取尽可能多的10-s,然后获取尽可能多的2-s

def best(N):
    print(N, end = ":  ")
    if (N % 2):
        print("5", end = "    ")
        N = N - 5
    if N >= 10:
        print("10*", N//10, end = "    ")
        N = N % 10
    if N > 1:
        print("2*", N//2, end = "    ")

21:  5    10* 1    2* 3    
22:  10* 2    2* 1    
23:  5    10* 1    2* 4    
24:  10* 2    2* 2    

通常,您可以使用动态编程找到最佳解决方案。

第一种方法是“记忆化”-您必须通过选择最佳解决方案来实现递归方法,然后将存储的中间结果添加到hashmap或其他结构中。简单的实现:

S = [2, 3, 5, 7, 11]
dic = {}

def rec(summ):
    if summ == 0:
        return 0
    rd = dic.get(summ)
    if rd != None:
        return rd
    minn = 9999999999999
    for s in S:
        if s <= summ:
            minn = min(minn, 1 + rec(summ - s))
    dic[summ] = minn
    return minn

N = 1000
print(rec(N))

>>92

另一种方法是使用表格-使用第一项填充最佳结果,然后使用第二项更新解决方案,依此类推。

伪代码

make int array A of size N+1
make int array P of size N+1
fill A[] with large value (MaxInt` or at least `N/min(S)) 
A[0] = 0
for s in S:  //coin value
    for (i = s; i <= N; i++)  
        if A[i - s] < A[i] + 1    //using this coin we can get better result for sum i
             A[i] = A[i - s] + 1
             P[i] = s            //write down coin for this sum

现在,我们拥有数量最多的A[N],并且可以使用P[N], P[N - P[N]]...序列检索所需的硬币。

有效的Python代码

S = [2, 3, 5, 7, 11]
N = 17
A = [0] + [10000] * N
P = [0] * (N + 1)
for s in S:  #coin value
    for i in range(s, N + 1):
        if A[i - s] < A[i] + 1:    #using this coin we can get better result for sum i
             A[i] = A[i - s] + 1
             P[i] = s            #write down coin for this sum

print(A) #for reference
i = N
while i > 0:
    print(P[i], end = " ")
    i = i - P[i]

>> [0, 10000, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2, 2, 2, 3, 2, 3]
>> 11 3 3 

注意-如果每个硬币只能使用一次,则必须向后进行内循环,以避免多次添加相同的硬币

答案 1 :(得分:1)

由于硬币数量少,并且由于数量可能很大,因此回溯可能会提供一个好的解决方案

这是C ++的实现
(注意:此代码已发布,但我找不到该帖子。问题已删除吗?)

首先对硬币进行降序排序,以加快搜索速度。
为了最大程度地减少硬币数量,我们首先尝试使用最大可能数量的最大硬币数量的解决方案。
在给定的搜索中,如果当前的硬币数量大于当前的最小硬币数量,我们将停止搜索(“过早放弃”)。

在代码中,“ UP”表示我们将考虑添加价值较低的硬币

“下降”表示我们将尝试减少高价值硬币的数量。

在给定的步骤中,我们维护一个对应于每个硬币值的硬币数量的数组

#include    <iostream>
#include    <vector>
#include    <algorithm>
#include    <numeric>

//  The order of array coins is modified

std::vector<int> get_change(std::vector<int>& coins, int amount) {
    std::vector<int> n_coins(coins.size(), 0);
    std::vector<int> n_coins_opt(coins.size(), 0);
    int n = coins.size();

    std::sort(coins.begin(), coins.end(), std::greater<int>());

    int sum = 0;    // current sum
    int i = 0;      // index of the coin being examined
    int n_min_coins = amount / coins[n - 1] + 1;
    int n_total_coins = 0;
    bool up_down = true;

    while (true) {          // UP
        if (up_down) {
            n_coins[i] = (amount - sum) / coins[i];     // max possible number of coins[i]
            sum += n_coins[i] * coins[i];
            n_total_coins += n_coins[i];
            if (sum == amount) {
                if (n_total_coins < n_min_coins) {
                    n_min_coins = n_total_coins;
                    n_coins_opt = n_coins;
                }
                up_down = false;
                sum -= n_coins[i] * coins[i];
                n_total_coins -= n_coins[i];
                n_coins[i] = 0;
                i--;
            }
            else {
                if (i == (n - 1) || (n_total_coins >= n_min_coins)) {   // premature abandon
                    sum -= n_coins[i] * coins[i];
                    n_total_coins -= n_coins[i];
                    n_coins[i] = 0;
                    up_down = false;
                    i--;
                }
                else {
                    i++;
                }
            }

        }
        else {            // DOWN
            if (i < 0) break;
            if (n_coins[i] == 0) {
                if (i == 0) break;
                i--;
            }
            else {
                sum -= coins[i];
                n_coins[i] --;
                n_total_coins--;
                i++;
                up_down = true;
            }
        }
    }

    return n_coins_opt;
}

int main() {
    std::vector<int> coins = {2, 5, 10};
    int amount = 1731;
    auto n_coins = get_change(coins, amount);

    int sum = std::accumulate (n_coins.begin(), n_coins.end(), 0);
    if (sum == 0) {
        std::cout << "no solution\n";
    } else {
        std::cout << amount << " = ";
        for (int i = 0; i < n_coins.size(); i++) {
                std::cout << n_coins[i] << "*" << coins[i] << " ";
        }
        std::cout << "\n";
    }
    return 1;
}