I had some free time these past few days, so I got back
to Project Euler: I like fooling around with the problems there,
particularly because the process reminds me of what it felt like
when I begun working with computers; the ecstasy of "pure" problem
solving, without the nasty side-effects of dealing with clients :‑)

In England the currency is made up of pound, £, and pence, p, and there are eight coins in general circulation:
1p, 2p, 5p, 10p, 20p, 50p, £1 (100p) and £2 (200p).

It is therefore possible to make £2 in the following way:1x£1, 1x50p, 2x20p, 1x5p, 1x2p, 3x1pHow many different ways can £2 be made using any number of coins?

Hmm...

Being the trigger-happy engineer that I am, I quickly tried to
brute-force:

#include<stdio.h>#include<signal.h>// We want to accumulate 200 cents (2 pounds)#define TARGET 200int coins[]={1,2,5,10,20,50,100,200};longlong total = 0LL;intnewCoin(int s){if(s == TARGET){ total ++;// landed right on 200 cents, increase totalreturn1;// and report "break out of the loop"}elseif(s>TARGET){// we overflowed, go back and report "break out of the loop"return1;}else{// recurse, adding each available coin in turnfor(int idx=0; idx<sizeof(coins)/sizeof(coins[0]); idx++){int earlyAbort =newCoin(s+coins[idx]);// if the callee reported non-zero, we breakif(earlyAbort)return0;}}return0;}voidpr(int i){// SIGINT (Ctrl-C) triggered reportsprintf("\n%lld\n", total);}intmain(){signal(SIGINT, pr);newCoin(0);// start the gameprintf("%lld\n", total);}

The process starts with newCoin(0), called from main.
This function will recursively call itself, until it either grows
the accumulated sum so far (its parameter) beyond 200 cents,
or it will reach exactly 200 cents - at which point the total
counter will record it as a successful combination.

Sure, sure, I am not handling the signals properly, printf is not re-entrant - but who cares,
I just want to quickly see if this solves the problem or not; Ctrl-C
will print how high total is, so trying it out...

To make matters worse, the speed at which total goes up makes it clear
that I am being an idiot: the blind recursion will obviously measure 1p+2p as a different combination
to 2p+1p. Order is not supposed to count; the problem statement says "how many different ways",
and this is clearly violating it.

Breaking it down, target: 1 cent

If I wanted to accumulate just 1 cent (column "Target in cents"), how many ways are there to do it, using coins less than or equal to 1p?

Only one way - so, fill up the cell of column "Using only 1p" with 1.

Moving on - what if I could use coins less than or equal to 2p?

Target in cents

Using only 1p

Using <= 2p

Using <= 5p

...

1

1

1

...

There is still only one way - using 1p. The 2p coins can't be used, since our target (a total of 1 cent for this line of the table) is less than 2p.

So all the first line fills up with 1s:

Target in cents

only 1p

<= 2p

<= 5p

<= 10p

<= 20p

<= 50p

<= 100p

<= 200p

1

1

1

1

1

1

1

1

1

Only one way to reach the target of 1 cent, no matter what coins I use.

Breaking it down, target: 2 cents

What if we move to a target of 2 cents?

Target in cents

only 1p

<= 2p

<= 5p

<= 10p

<= 20p

<= 50p

<= 100p

<= 200p

1

1

1

1

1

1

1

1

1

2

1

Well, using only 1p there is only one way: 1p+1p.

When using coins less than or equal to 2p, however, we can also do it via a single 2p coin - so there are now 2 ways:

Target in cents

only 1p

<= 2p

<= 5p

<= 10p

<= 20p

<= 50p

<= 100p

<= 200p

1

1

1

1

1

1

1

1

1

2

1

2

2

2

2

2

2

2

Additional coins don't help any - the total number of ways remains 2 (1p+1p,2p) throughout the rest of the line.

Breaking it down, target: 3,4,5,... cents

Thinking the same way:

Target in cents

only 1p

<= 2p

<= 5p

<= 10p

<= 20p

<= 50p

<= 100p

<= 200p

1

1

1

1

1

1

1

1

1

2

1

2

2

2

2

2

2

2

3

1

2

2

2

2

2

2

2

4

1

3

3

3

3

3

3

3

5

1

3

4

4

4

4

4

4

3 cents can be formed as (1p+1p+1p,1p+2p), so starting from column "<=2p", the number is 2

4 cents can be formed as (4x1p, 2x1p+1x2p, 2x2p), so starting from column "<=2p", the number is 3

5 cents can be formed as (5x1p, 3x1p+1x2p, 1x1p+2x2p, 1x5p), so starting from column "<=5p", the number is 4

etc...

So, now that we've done this process manually, what do we notice?

We notice that when forming the values in a line, the first column is always 1. There is only one way to form any target N, if you just use 1p coins: Nx1p.

We also notice that when filling up a cell, we check to see if the corresponding coin "fits" in. If it doesn't, we just copy the value of the cell on the left - i.e. the number of ways to form a target sum doesn't change, since we can't use this column's coin.

If the coin *does* fit, however, we then form a number by adding two things: (a) the number of ways we can form the target WITHOUT using the coin (which is on the cell on our left) plus (b) the number of ways we can form the target-columnCoin, i.e. the number of ways to form the remainder, if we subtract (i.e. use) the coin from our target.

For example, on line 5 (target: 5 cents) the highlighted cell of column "<=2p" is formed as 1+2:

There is 1 way to form a target of 5 cents using only 1p coins (the cell on the left has value 1)

If we use a coin of 2p, then the remainder is target-2p=5p-2p=3p,
which we can lookup above, on line 3, and see that there are 2 ways to reach it, using "<=2p".

This lookup is key - we basically reuse previous calculations with a single lookup.

OK, we can now write it down in Python...

#!/usr/bin/env python# the 8 coins correspond to 8 columnscoins =[1,2,5,10,20,50,100,200]TARGET=200matrix ={}for y inxrange(0, TARGET+1):# There is only one way to form a target sum N# via 1-cent coins: use N 1-cents! matrix[y,0]=1# equivalent to matrix[(y,0)]=1for y inxrange(0, TARGET+1):print y,":",1,for x inxrange(1,len(coins)): matrix[y, x]=0# Is the target big enough to accomodate coins[x]?if y>=coins[x]:# If yes, then the number of ways to form# the target sum are obtained via:## (a) the number of ways to form this target# using ONLY coins less than column x# i.e. matrix[y][x-1] matrix[y, x]+= matrix[y, x-1]# plus# (b) the number of ways to form this target# when USING the coin of column x# which means for a remainder of y-coins[x]# i.e. matrix[y-coins[x]][x] matrix[y, x]+= matrix[y-coins[x], x]else:# if the target is not big enough to allow# usage of the coin in column x,# then just copy the number of ways from the# column to the left (i.e. with smaller coins) matrix[y, x]= matrix[y, x-1]print matrix[y, x],print

...and marvel at seeing it run infinitely faster than brute-force (finishes in less than 30 milliseconds):

The comments on this website require the use of JavaScript. Perhaps your browser isn't
JavaScript capable or the script is not being run for another reason. If you're
interested in reading the comments or leaving a comment behind please try again with a
different browser or from a different connection.