Jason
Jason

Reputation: 11363

Coin change algorithm and pseudocode: Need clarification

I'm trying to understand the coin change problem solution, but am having some difficulty.

At the Algorithmist, there is a pseudocode solution for the dynamic programming solution, shown below:

n = goal number
S = [S1, S2, S3 ... Sm]

function sequence(n, m)
   //initialize base cases

   for i = 0 to n
     for j = 0 to m
       table[i][j] = table[i-S[j]][j] + table[i][j-1]

This is a pretty standard O(n^2) algorithm that avoids recalculating the same answer multiple times by using a 2-D array.

My issue is two-fold:

  1. How to define the base cases and incorporate them in table[][] as initial values
  2. How to extract out the different sequences from the table

Regarding issue 1, there are three base cases with this algorithm:

How to incorporate them into table[][], I am not sure. Finally, I have no idea how to extract out the solution set from the array.

Upvotes: 1

Views: 4107

Answers (1)

derekhh
derekhh

Reputation: 5532

We can implement a dynamic programming algorithm in at least two different approaches. One is the top-down approach using memoization, the other is the bottom-up iterative approach.

For a beginner to dynamic programming, I would always recommend using the top-down approach first since this will help them understand the recurrence relationships in dynamic programming.

So in order to solve the coin changing problem, you've already understood what the recurrence relationship says:

table[i][j] = table[i-S[j]][j] + table[i][j-1]

Such a recurrence relationship is good but is not that well-defined since it doesn't have any boundary conditions. Therefore, we need to define boundary conditions in order to ensure the recurrence relationship could successfully terminate without going into an infinite loop.

So what will happen when we try to go down the recursive tree?

If we need to calculate table[i][j], which means the number of approaches to change i using coins from type 0 to j, there are several corner cases we need to handle:

1) What if j == 0?

If j == 0 we will try to solve the sub-problem table(i,j-1), which is not a valid sub-problem. Therefore, one boundary condition is:

if(j==0) {
  if(i==0) table[i][j] = 1;
  else table[i][j] = 0;
}

2) What if i - S[j] < 0?

We also need to handle this boundary case and we know in such a condition we should either not try to solve this sub-problem or initialize table(i-S[j],j) = 0 for all of these cases.

So in all, if we are going to implement this dynamic programming from a top-down memoization approach, we can do something like this:

int f(int i, int j) {
  if(calc[i][j]) return table[i][j];
  calc[i][j] = true;
  if(j==0) {
    if(i==0) return table[i][j]=1;
    else return table[i][j]=0;
  }
  if(i>=S[j])
    return table[i][j]=table[i-S[j][j]+table[i][j-1];
  else
    return table[i][j]=table[i][j-1];
}

In practice, it's also possible that we use the value of table arrays to help track whether this sub-problem has been calculated before (e.g. we can initialize a value of -1 means this sub-problem hasn't been calculated).

Hope the answer is clear. :)

Upvotes: 2

Related Questions