Recently I challenged my co-worker to write an algorithm to solve this problem:
Find the least number of coins required that can make any change from
Here's a simple version in Python.
#!/usr/bin/env python
required = []
coins = [25, 10, 5, 1]
t = []
for i in range(1, 100):
while sum(t) != i:
for c in coins:
if sum(t) + c <= i:
t.append(c)
break
for c in coins:
while t.count(c) > required.count(c):
required.append(c)
del t[:]
print required
When run, it prints the following to stdout.
[1, 1, 1, 1, 5, 10, 10, 25, 25, 25]
The code is pretty self-explanatory (thanks Python!), but basically the algorithm is to add the largest coin available that doesn't put you over the current total you're shooting for into your temporary list of coins (t in this case). Once you find the most efficient set of coins for a particular total, make sure there are at least that many of each coin in the required list. Do that for every total from 1 to 99 cents, and you're done.
In general, if you have your coins COIN[] and your "change range" 1..MAX, the following should find the maximum number of coins.
Initialise array CHANGEVAL[MAX] to -1
For each element coin in COIN:
set CHANGEVAL[coin] to 1
Until there are no more -1 in CHANGEVAL:
For each index I over CHANGEVAL:
if CHANGEVAL[I] != -1:
let coincount = CHANGEVAL[I]
For each element coin in COIN:
let sum = coin + I
if (COINS[sum]=-1) OR ((coincount+1)<COINS[sum]):
COINS[sum]=coincount+1
I don't know if the check for coin-minimality in the inner conditional is, strictly speaking, necessary. I would think that the minimal chain of coin-additions would end up being correct, but better safe than sorry.
Here goes my solution, again in Python and using dynamic programming. First I generate the minimum sequence of coins required for making change for each amount in the range 1..99, and from that result I find the maximum number of coins needed from each denomination:
def min_any_change():
V, C = [1, 5, 10, 25], 99
mxP, mxN, mxD, mxQ = 0, 0, 0, 0
solution = min_change_table(V, C)
for i in xrange(1, C+1):
cP, cN, cD, cQ = 0, 0, 0, 0
while i:
coin = V[solution[i]]
if coin == 1:
cP += 1
elif coin == 5:
cN += 1
elif coin == 10:
cD += 1
else:
cQ += 1
i -= coin
if cP > mxP:
mxP = cP
if cN > mxN:
mxN = cN
if cD > mxD:
mxD = cD
if cQ > mxQ:
mxQ = cQ
return {'pennies':mxP, 'nickels':mxN, 'dimes':mxD, 'quarters':mxQ}
def min_change_table(V, C):
m, n, minIdx = C+1, len(V), 0
table, solution = [0] * m, [0] * m
for i in xrange(1, m):
minNum = float('inf')
for j in xrange(n):
if V[j] <= i and 1 + table[i - V[j]] < minNum:
minNum = 1 + table[i - V[j]]
minIdx = j
table[i] = minNum
solution[i] = minIdx
return solution
Executing min_any_change()
yields the answer we were looking for: {'pennies': 4, 'nickels': 1, 'dimes': 2, 'quarters': 3}
. As a test, we can try removing a coin of any denomination and checking if it is still possible to make change for any amount in the range 1..99:
from itertools import combinations
def test(lst):
sums = all_sums(lst)
return all(i in sums for i in xrange(1, 100))
def all_sums(lst):
combs = []
for i in xrange(len(lst)+1):
combs += combinations(lst, i)
return set(sum(s) for s in combs)
If we test the result obtained above, we get a True
:
test([1, 1, 1, 1, 5, 10, 10, 25, 25, 25])
But if we remove a single coin, no matter what denomination, we'll get a False
:
test([1, 1, 1, 5, 10, 10, 25, 25, 25])
Solution with greedy approach in java is as below :
public class CoinChange {
public static void main(String args[]) {
int denominations[] = {1, 5, 10, 25};
System.out.println("Total required coins are " + greeadApproach(53, denominations));
}
public static int greeadApproach(int amount, int denominations[]) {
int cnt[] = new int[denominations.length];
for (int i = denominations.length-1; amount > 0 && i >= 0; i--) {
cnt[i] = (amount/denominations[i]);
amount -= cnt[i] * denominations[i];
}
int noOfCoins = 0;
for (int cntVal : cnt) {
noOfCoins+= cntVal;
}
return noOfCoins;
}
}
But this works for single amount. If you want to run it for range, than we have to call it for each amount of range.
Edit: As the commenters have noted, I have misinterpreted the question. (The question is very similar to a basic CS problem I see students at the college having to solve...) waves hand This is not the answer you are looking for. That said, while the original answer is wrong, we can use it as a stepping stone to an O(n) solution.
So, take the wrong answer below, which only solves for a single value (ie, the minimum coinage required for 68 cents) and simply run it for every value.
changes = []
for amount in xrange(1, 100): # [1, 99]
changes.append( run_the_algo_below( amount ) )
# Take the maximum for each coin type.
# ie, if run_the_algo_below returns (q, d, n, p):
change = [0, 0, 0, 0]
for c in changes:
change = [max(c[i], change[i] for i in xrange(0, 4)]
Now, this will certainly give you a valid answer, but is it a minimal answer? (this is the harder part. Currently my gut says yes, but I'm still thinking about this one...)
(The wrong answer)
Wow. Loops? Dynamic programming? Really folks?
In Python:
amount = ( your_amount_in_cents )
quarters = amount // 25
dimes = amount % 25 // 10
nickels = amount % 25 % 10 // 5
pennies = amount % 25 % 10 % 5
Probably some of those modulo operations can be simplified...
This isn't hard, you just need to think about how you make change in real life. You give out quarters until adding another quarter would put you over the desired amount, you give out dimes until adding another dime would put you over the desired amount, so on. Then, convert to mathematical operations: modulo and division. Same solution applies for dollars, converting seconds into HH:MM:SS, etc.
A vb version
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
For saleAMT As Decimal = 0.01D To 0.99D Step 0.01D
Dim foo As New CashDrawer(0, 0, 0)
Dim chg As List(Of Money) = foo.MakeChange(saleAMT, 1D)
Dim t As Decimal = 1 - saleAMT
Debug.WriteLine(t.ToString("C2"))
For Each c As Money In chg
Debug.WriteLine(String.Format("{0} of {1}", c.Count.ToString("N0"), c.moneyValue.ToString("C2")))
Next
Next
End Sub
Class CashDrawer
Private _drawer As List(Of Money)
Public Sub New(Optional ByVal QTYtwoD As Integer = -1, _
Optional ByVal QTYoneD As Integer = -1, _
Optional ByVal QTYfifty As Integer = -1, _
Optional ByVal QTYquarters As Integer = -1, _
Optional ByVal QTYdimes As Integer = -1, _
Optional ByVal QTYnickels As Integer = -1, _
Optional ByVal QTYpennies As Integer = -1)
_drawer = New List(Of Money)
_drawer.Add(New Money(2D, QTYtwoD))
_drawer.Add(New Money(1D, QTYoneD))
_drawer.Add(New Money(0.5D, QTYfifty))
_drawer.Add(New Money(0.25D, QTYquarters))
_drawer.Add(New Money(0.1D, QTYdimes))
_drawer.Add(New Money(0.05D, QTYnickels))
_drawer.Add(New Money(0.01D, QTYpennies))
End Sub
Public Function MakeChange(ByVal SaleAmt As Decimal, _
ByVal amountTendered As Decimal) As List(Of Money)
Dim change As Decimal = amountTendered - SaleAmt
Dim rv As New List(Of Money)
For Each c As Money In Me._drawer
change -= (c.NumberOf(change) * c.moneyValue)
If c.Count > 0 Then
rv.Add(c)
End If
Next
If change <> 0D Then Throw New ArithmeticException
Return rv
End Function
End Class
Class Money
'-1 equals unlimited qty
Private _qty As Decimal 'quantity in drawer
Private _value As Decimal 'value money
Private _count As Decimal = 0D
Public Sub New(ByVal theValue As Decimal, _
ByVal theQTY As Decimal)
Me._value = theValue
Me._qty = theQTY
End Sub
ReadOnly Property moneyValue As Decimal
Get
Return Me._value
End Get
End Property
Public Function NumberOf(ByVal theAmount As Decimal) As Decimal
If (Me._qty > 0 OrElse Me._qty = -1) AndAlso Me._value <= theAmount Then
Dim ct As Decimal = Math.Floor(theAmount / Me._value)
If Me._qty <> -1D Then 'qty?
'limited qty
If ct > Me._qty Then 'enough
'no
Me._count = Me._qty
Me._qty = 0D
Else
'yes
Me._count = ct
Me._qty -= ct
End If
Else
'unlimited qty
Me._count = ct
End If
End If
Return Me._count
End Function
ReadOnly Property Count As Decimal
Get
Return Me._count
End Get
End Property
End Class
End Class