Number of ways to make change for amount N

2019-04-06 17:46发布

问题:

I came across this problem:

http://www.geeksforgeeks.org/dynamic-programming-set-7-coin-change/

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, how many ways can we make the change? The order of coins doesn’t matter.

For example, for N = 4 and S = {1,2,3}, there are four solutions: {1,1,1,1},{1,1,2},{2,2},{1,3}. So output should be 4. For N = 10 and S = {2, 5, 3, 6}, there are five solutions: {2,2,2,2,2}, {2,2,3,3}, {2,2,6}, {2,3,5} and {5,5}. So the output should be 5.

I came up with the solution:

// recurrence relation
count[N] = count[N-d] for all denomination <= N

Source code
-----------

public static int numWays(int N, int[] denoms) {
  if (N == 0)
     return 0;

  int[] ways = new int[N+1];
  ways[0] = 1;

  for (int i=1; i<=N; i++) {
     ways[i] = 0;
     for (int d : denoms) {
        if (d <= i) {
           ways[i] += ways[i-d];
        }
     }
  }

  return ways[N];
}

But this counts duplicates which have the same denominations but in different order. For example, if denominations = {1,2} and N=3, then it counts {1,1,1}, {2,1}, {1,2} which has duplicate entry {1,2}.

I see that the DP solution described in the link here avoids duplicates. I understand how the recurrence relation works and all but I am not able to understand how it is able to avoid the duplicates while my solution is not. Please explain the idea behind.

回答1:

Let C(i, j) the number of ways to make the total i using coins of denominations S1, ..., Sj. Your code implements the following recurrence (ordered ways).

C(i, m) | i <  0 = 0
        | i == 0 = 1
        | i >  0 = sum_{j = 1}^m C(i - Sj, m)

The linked code implements a different recurrence (unordered ways).

C(i, j) | i <  0           = 0
        | i == 0           = 1
        | i >  0 && j <= 0 = 0
        | i >  0 && j >  0 = C(i - Sj, j) + C(i, j - 1)

The difference between the two codes is subtle: more or less just how the loops are nested. Yours adds all of the terms for i before moving on to i + 1, but the linked code adds the j term for each i, then the j + 1 term for each i, etc. As a result, when the linked code considers the possibility of using a denomination-Sj coin from subtotal i, it implicitly is considering only those solutions that continue with coins of denominations S1, ..., Sj, because the current total for i - Sj does not include the possibilities that use other coins.