打印零钱找零算法的最优解。

huangapple go评论80阅读模式
英文:

Print optimal solution for coin change algorithm

问题

给定一个价值 N,如果我们想要为 N 美分找零,而且我们有无限数量的每个面值为 S = { S1,S2,...,Sm} 的硬币,那么找零 N 美分的最佳方法是什么。

示例:

S = {2,5,10}
N = 6,则最佳解决方案是: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() {
    // 创建大小为 N+1 的整数数组 A
    int N = 6;
    int[] A = new int[N + 1];
    // 创建大小为 N+1 的整数数组 P
    int[] P = new int[N + 1];
    // 使用较大的值(len(S) + 1)填充 A[]
    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) {// 硬币面值
        for (int i = s; i <= N; i++) {
            if (A[i - s] < A[i] + 1) { // 使用这个硬币可以得到更好的结果
                A[i] = A[i - s] + 1;
                P[i] = s; // 记录这个和的硬币
            }
        }
    }
    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

如何修复这段代码?

英文:

Given a value N, if we want to make change for N cents, and we have infinite supply of each of S = { S1, S2, .. , Sm} valued coins, what is the optimal way to make change for N cents.

Example:

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

I have below working code:

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 &gt; n) {
			return;
		}

		for (int i = 0; i &lt; 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 &lt; index; i++) {
			System.out.print(combo[i] + &quot; &quot;);
		}
		System.out.println();
	}

Output:

2 2 2 2 2 
5 5 
10 

Here I just need to optimal combination with less number of coins so only 10 in this example code.

But this code uses recursive approach, my value for N is Long type so as the value of N increases I am getting stackoverflow error.

The recursive approach I am following here is not correct, What is the correct way to solve this problem?

Update:

Based on MBo answer I tried below program, but I am not able to get the correct results:

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 &lt; A.length; i++) {
			A[i] = lengthOfS + 1;
		}

		A[0] = 0;

		for (int s : S) {// coin value
			for (int i = s; i &lt;= N; i++) {
				if (A[i - s] &lt; 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 &lt; A[N]; i++) {
			int result = idx - P[idx];
			System.out.println(result); // 4 2 0
			idx = result;
		}
	}

This code prints:

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

4
2
0

How to fix this code?

答案1

得分: 3

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

N=1,3 时没有解决方案。

如果 N 为奇数,必须使用 5,即 N=N-5

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

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="    ")

# 以下是对应的输出示例
best(21)  # 输出:5    10* 1    2* 3
best(22)  # 输出:10* 2    2* 1
best(23)  # 输出:5    10* 1    2* 4
best(24)  # 输出:10* 2    2* 2

通常可以使用动态规划找到最优解决方案。

第一种方式是 "记忆化" - 必须实现具有最佳解决方案选择的递归方法,然后在哈希映射或另一个结构中添加存储中间结果。简单实现如下:

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

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

伪代码如下:

创建大小为 N+1 的整数数组 A
创建大小为 N+1 的整数数组 P
用较大的值填充 A[]例如 MaxInt 或至少 N/min(S)
A[0] = 0
for s in S:  #硬币面值
    for i in range(s, N + 1):  
        if A[i - s] < A[i] + 1  #使用此硬币可以获得更好的结果,使总和为 i
             A[i] = A[i - s] + 1
             P[i] = s  #记录此总和的硬币

现在我们有 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:  #硬币面值
    for i in range(s, N + 1):
        if A[i - s] < A[i] + 1:  #使用此硬币可以获得更好的结果,使总和为 i
             A[i] = A[i - s] + 1
             P[i] = s  #记录此总和的硬币

print(A)  #供参考
i = N
while i > 0:
    print(P[i], end=" ")
    i = i - P[i]

# 输出:11 3 3
英文:

For fixed set S = {2, 5, 10} solution is rather simple:

No solutions for N=1,3

if N is odd, you must use 5 - so N=N-5

Now use greedy approach: get as much 10-s as possible, then as much 2-s as possible

def best(N):
    print(N, end = &quot;:  &quot;)
    if (N % 2):
        print(&quot;5&quot;, end = &quot;    &quot;)
        N = N - 5
    if N &gt;= 10:
        print(&quot;10*&quot;, N//10, end = &quot;    &quot;)
        N = N % 10
    if N &gt; 1:
        print(&quot;2*&quot;, N//2, end = &quot;    &quot;)

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

In general you can find optimal solution using dynamic programming.

The first way is "memoization" - you have to implement recursive approach with the choice of the best solution, then add storing intermediate results in hashmap or another structure. Simple implementation:

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 &lt;= summ:
            minn = min(minn, 1 + rec(summ - s))
    dic[summ] = minn
    return minn
      
N = 1000
print(rec(N))

&gt;&gt;92

Another way is using table - you fill it with the best possible results using the first item, then update solution using the second item and so on.

Pseudocode

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 &lt;= N; i++)  
        if A[i - s] &lt; 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

Now we have A[N] with the best count, and can retrieve needed coins using P[N], P[N - P[N]]... sequence.

Working Python code

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] &lt; 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 &gt; 0:
    print(P[i], end = &quot; &quot;)
    i = i - P[i]

&gt;&gt; [0, 10000, 1, 1, 2, 1, 2, 1, 2, 2, 2, 1, 2, 2, 2, 3, 2, 3]
&gt;&gt; 11 3 3 

Note - if we can use every coin only once, we have to make inner loop in backward direction to avoid multiple adding the same coin

答案2

得分: 1

以下是一个C++代码实现的示例:

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

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;
}
英文:

As the number of coins is small, and since the amount can be large, it is likely that backtracking can provide a good solution

Here is an implementation in C++
(Note: this code was already posted, but I can't find the post. Question deleted ?)

The coins are first sorted if descending order, to fasten the search.
In order to minimize the number of coins, we first try solutions with maximum possible number of coins of largest value.
In a given search, if the current number of coins is larger than the current minimum number of coins, we stopped the search ("premature abandon").

In the code, "UP" means that we will consider adding coins with a lower value

"DOWN" means that we will try to decrease the number of coins of higher value.

At a given step, we maintain an array corresponding to the number of coins for each coin value

#include    &lt;iostream&gt;
#include    &lt;vector&gt;
#include    &lt;algorithm&gt;
#include    &lt;numeric&gt;
//	The order of array coins is modified
std::vector&lt;int&gt; get_change(std::vector&lt;int&gt;&amp; coins, int amount) {
std::vector&lt;int&gt; n_coins(coins.size(), 0);
std::vector&lt;int&gt; n_coins_opt(coins.size(), 0);
int n = coins.size();
std::sort(coins.begin(), coins.end(), std::greater&lt;int&gt;());
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 &lt; 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 &gt;= 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 &lt; 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&lt;int&gt; 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 &lt;&lt; &quot;no solution\n&quot;;
} else {
std::cout &lt;&lt; amount &lt;&lt; &quot; = &quot;;
for (int i = 0; i &lt; n_coins.size(); i++) {
std::cout &lt;&lt; n_coins[i] &lt;&lt; &quot;*&quot; &lt;&lt; coins[i] &lt;&lt; &quot; &quot;;
}
std::cout &lt;&lt; &quot;\n&quot;;
}
return 1;
}

huangapple
  • 本文由 发表于 2020年5月29日 08:52:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/62076917.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定