Here's a solution generalizing @grodzi's, in Python. The solution depends on the coin denominations not having a common factor; if they do, you adjust the solution by dividing everything by the highest common factor, and rejecting inputs where that division has a remainder.
For sufficiently large inputs, every sum is possible. "Sufficiently large" in this case means after the first run of c sums that are possible, where c is the largest coin denomination. We can do dynamic programming to compute solutions up until a run of length c is found, and then every sum can be solved by taking the appropriate number of coins of denomination c away, reducing the sum to within this range.
The time complexity of the initialisation stage is at most O(f(coins) * len(coins)) where the function f gives the Frobenius number of the set of coin denominations. The time complexity of the make_change
method is O(len(coins)) plus the complexity of doing the integer division and remainder operations, which would be O(1) in a language with bounded integers.
from collections import Counter
class ChangeMaker:
def __init__(self, *denominations):
denominations = sorted(denominations, reverse=True)
self.c = denominations[0]
self.cache = [Counter()]
def solve(n):
for d in denominations:
if d <= n and self.cache[n - d] is not None:
return Counter({ d: 1 }) + self.cache[n - d]
return None
run_length = 0
while run_length < self.c:
r = solve(len(self.cache))
self.cache.append(r)
if r is not None:
run_length += 1
else:
run_length = 0
def make_change(self, n):
if n < len(self.cache):
return self.cache[n]
else:
diff = n - len(self.cache) + self.c
div = diff // self.c
rem = diff % self.c
cached = self.cache[len(self.cache) - self.c + rem]
return Counter({ self.c: div }) + cached
Example:
>>> c = ChangeMaker(2, 5, 10)
>>> c.cache
[Counter(),
None,
Counter({2: 1}),
None,
Counter({2: 2}),
Counter({5: 1}),
Counter({2: 3}),
Counter({5: 1, 2: 1}),
Counter({2: 4}),
Counter({2: 2, 5: 1}),
Counter({10: 1}),
Counter({2: 3, 5: 1}),
Counter({10: 1, 2: 1}),
Counter({2: 4, 5: 1})]
>>> c.make_change(123456789011)
Counter({10: 12345678900, 2: 3, 5: 1})
>>> c.make_change(123456789013)
Counter({10: 12345678900, 2: 4, 5: 1})