Example Program #2: The Coin Problem

- November 18, 2009
- James S. Plank
- Directory:
**/home/plank/cs302/Notes/DynamicProgramming**

Here's the problem: Given a list of

We start with **Step 1:** finding the recursion. Often the best way to do this is to
try some examples. I think its easier to think of postage stamps instead of coins, because
they can come in wacky denominations. And then I think of the sum as a total amount of postage,
and I'm trying to put stamps on the package to equal the amount of postage.
So, for example, suppose our denominations are 1, 5, 6 and 10, and our sum is 11. The
minimum way of constructing the sum is one 5 and one 6, or one 10 and one 1. Since the problem asks for
the minimum number of "coins", the answer is 2. Similarly, if our sum is 18, the answer is three: three 6's.

Let's make a quick table of sums and answers:

S |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |

Answer |
1 |
2 |
3 |
4 |
1 |
1 |
2 |
3 |
4 |
1 |
2 |
2 |
3 |
4 |
2 |
2 |
3 |
3 |

Coins |
1 |
1,1 |
1,1,1 |
1,1,1,1 |
5 |
6 |
6,1 |
6,1,1 |
6,1,1,1 |
10 |
5,6 |
6,6 |
6,6,1 |
6,6,1,1 |
10,5 |
10,6 |
10,6,1 |
6,6,6 |

There doesn't appear to be a nice pattern, but think recursively. If I want to make the sum *S*,
and my coins are 1, 5, 6 and 10, then I can make the sum in four ways:

- I can make the sum
*S-1*and add a 1 coin. - I can make the sum
*S-5*and add a 5 coin. - I can make the sum
*S-6*and add a 6 coin. - I can make the sum
*S-10*and add a 10 coin.

See how to write the procedure? You loop through all the values, making recursive calls.
It's in
**coins1.cpp**. I use *s+1* as a sentinel value for the minimum,
and if the sum cannot be constructed, then I return -1.

#include <iostream> #include <cstdlib> #include <vector> using namespace std; class Coins { public: vector <int> v; int M(int i); }; int Coins::M(int s) { int i, j, min, sv; /* We initialize the minimum by sentinelizing it to an impossible value. */ min = s+1; /* Loop through all of the coins. */ for (i = 0; i < v.size(); i++) { /* If our sum equals a coin, then we're done -- return one. */ if (s == v[i]) return 1; /* Otherwise, simulate using the coin by calling M() on the sum minus the coin's value. If that's better than our current minimum, update the minimum. */ if (s > v[i]) { j = M(s-v[i]) + 1; if (j != 0 && j < min) min = j; } } /* The min equals the sentinel, then it's impossible, return -1. Otherwise, return the minimum. */ if (min == s+1) return -1; return min; } int main(int argc, char **argv) { Coins c; int i; int sum; if (argc != 2) { cerr << "usage: coins s -- values on standard input\n"; exit(1); } sum = atoi(argv[1]); while (cin >> i) c.v.push_back(i); cout << c.M(sum) << endl; } |

A quick test shows that it works:

UNIX>However, like most simple recursive implementations, it is too slow. It bogs down whensh>for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ; do echo $i `echo 1 5 6 10 | coins1 $i`; done1 1 2 2 3 3 4 4 5 1 6 1 7 2 8 3 9 4 10 1 11 2 12 2 13 3 14 4 15 2 16 2 17 3 18 3 >

int Coins::M(int s) { int i, j, min, sv; if (cache.size() <= s) cache.resize(s+1, -2); if (cache[s] != -2) return cache[s]; if (s == 0) { cache[s] = 0; return 0; } min = s+1; for (i = 0; i < v.size(); i++) { if (s >= v[i]) { j = M(s-v[i]) + 1; if (j != 0 && j < min) min = j; } } if (min == s+1) min = -1; cache[s] = min; return min; } |

This is much faster:

UNIX>Forecho 1 5 6 10 | coins2 50551 UNIX>echo 1 5 6 10 | coins2 5057507 UNIX>

int Coins::M(int s) { int i, j, val, min, sv; vector <int> cache; cache.resize(s+1); cache[0] = 0; for (j = 1; j <= s; j++) { min = s+1; for (i = 0; i < v.size(); i++) { if (j >= v[i]) { val = cache[j-v[i]] + 1; if (val != 0 && val < min) min = val; } } if (min == s+1) min = -1; cache[j] = min; } return cache[s]; } |

Finally, there is a way to do step four if you think about it. This about that last call above --
"**echo 1 5 6 10 | coins2 5057**". Does your cache really need 5057 elements?
If you don't see the answer, ask me in class -- this is a good test question.