Given a value n cents and m type of coins with values { c1, c2, .. , cm} cents each. How many ways can we make the n cents by using the coins as many times as you want?
For example, for n = 5 and C = {1, 2, 5}, there are 4 solutions {{1,1,1,1,1}, {1,1,1,2}, {1,2,2}, {5}}.
We observe that we can solve this problem by solving the subproblem of finding solution for a lower sum. That is we can use a DP approach. We use DP when –
- We can formulate the solution combining solutions of smaller subproblem, and.
- There are overlapping subproblems.
Optimal Substructure
We can see that we can find the number ways we can make n using C={ c1, c2, .. , cm} coins by combining solution of making (n-cm) and including last coin cm to current solution; or we can simply ignore the last coin cm and try to find solution with remaining coins.
let, cs(C, n, m) be the number of solutions of making n using m coins C = {c1, c2, …, cm}. The we can write the following recursive formula,
cs(C, n, m) = cs(C, n-cm, m) + cs(C, n, m-1).
It is now clear that coin sum has a optimal substructure.
Overlapping Subproblems
If we write down the pictorial representation of the above recursion the we can see from the following figure that subproblems (such as cs({1,2},5,2) ) are getting repeated. That’s is we have overlapping subproblems and so we can use a table to store previous computation of solution to a sub problem.
cs({1,2,5},10,3)
/ \
/ \
cs({1,2,5},5,3) cs({1,2},5,2)
/ \ / \
/ \ / \
cs({1,2,5},0,3) cs({1,2},5,2) cs({1,2},3,2) cs({1}, 5, 1)
/ / \ / \ / \
/ / \ / \ / \
cs({1,2},0,2) C({1},2,1) cs({1,2},1) C({1},3) cs({1}, 4) cs({}, 5)
/ \ / \ / \ / \
/ \ / \ / \ / \
. . . . . . C({1}, 3) C({}, 4)
/ \
/ \
Below is a O(nm) solution for the coin sum problem using DP.
public static int coinSum(final int[] coins, final int sum) {
final int m = coins.length;
final int[][] csTable = new int[sum + 1][m + 1];
// base cases: if m == 0 then no solution for any sum
for (int i = 0; i <= sum; i++) {
csTable[i][0] = 0;
}
// base case: if sum = 0 then there is only one solution for any input set: just take none of each of the items.
for (int j = 0; j <= m; j++) {
csTable[0][j] = 1;
}
for (int i = 1; i <= sum; i++) {
for (int j = 1; j <= m; j++) {
// solutions excluding coins[j]
final int s1 = csTable[i][j - 1];
// solutions including coins[j]
// look at the column index in csTable[i - coins[j - 1]][j]. This is not j-1 as we can use as much coin
// of type j as we like.
final int s2 = (i - coins[j - 1]) >= 0 ? csTable[i - coins[j - 1]][j] : 0;
csTable[i][j] = s1 + s2;
}
}
return csTable[sum][m];
}
There could be another variant of this problem:
Given a non-negative number n. Find out how many ways n can be made by summing up non-negative integers.
This can be solved by coins sum problem as cs({0, 1, 2, …n-1}, n, n).
