What you are trying to solve is a variant of the coin change problem. Here you are looking for the smallest number of changes or the minimum number of coins summed up to a given amount.
Consider the simple case when your array
c = [1, 2, 3]
you write 5 as a combination of elements from C and want to know what is the shortest such combination. Here C is a set of coin values, and 5 is the amount for which you want to receive changes.
Record all possible combinations:
1 + 1 + 1 + 1 + 1 1 + 1 + 1 + 2 1 + 2 + 2 1 + 1 + 3 2 + 3
Please note that the two combinations coincide before reordering, therefore, for example, 2 + 3 = 3 + 2.
There is an amazing result here that is not obvious at first glance, but it is very easy to prove. If you have any sequence of coins / values, which is a sequence of minimum length that sums up to a given value, regardless of how you divide this sequence, the two parts will also be sequences of minimum length for the corresponding amounts.
For example, if c[3] + c[1] + c[2] + c[7] + c[2] + c[3]
add to S
, and we know that 6
is a sequence of the minimum length of elements from c
which adds up to S
, then if you split
| S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3] |
you have that 4
is the minimum length for sequences that add up to c[3] + c[1] + c[2] + c[7]
and 2
is the minimum length for sequences that add up to c[2] + c[3]
.
| S = c[3] + c[1] + c[2] + c[7] | + c[2] + c[3] | = S_left + S_right
How to prove it? Contradiction, suppose that the length of S_left
not optimal, that is, a shorter sequence that is added before S_left
. But then we could write S
as the sum of this shorter sequence and S_right
, which contradicts the fact that the length of S
minimal. □
Since this is true no matter how you split the sequence, you can use this result to create a recursive algorithm that follows the principles of the dynamic programming paradigm (solving smaller problems, possibly skipping calculations that won't be used, memoization or tracking the calculated values and finally, combining the results).
OK, therefore, in the small example above, we will consider how we will solve the problem using the dynamic programming approach: suppose we want to find the shortest sequence of elements from c = [1, 2, 3]
to write the sum 5
. We solve the subtasks obtained by subtracting one coin: 5 - 1
, 5 - 2
and 5 - 3
, we take the smallest solution to these subtasks and add 1 (the missing coin).
So we can write something like
shortest_seq_length([1, 2, 3], 5) = min( shortest_seq_length([1, 2, 3], 5-1), shortest_seq_length([1, 2, 3], 5-2), shortest_seq_length([1, 2, 3], 5-3) ) + 1
It is convenient to write the algorithm from the bottom up, starting with lower values of the sums that can be saved and used to generate large sums. We simply solve the problem for all possible values, starting from 1 and rising to the desired amount.
Here's the code in Python:
def shortest_seq_length(c, S): res = {0: 0}
Now this works, except when we cannot fill the memoization structure for all i
values. This is the case when we do not have a value of 1
in c
, therefore, for example, we cannot form the sum 1
if c = [2, 5]
and with the above function we get
shortest_seq_length([2, 3], 5)
So, to take care of this problem, one could, for example, use try / catch:
def shortest_seq_length(c, S): res = {0: 0} # res is the dictionary containing results for each sum res[i] = shortest_seq_length(c, i) for i in range(1, S+1): try: res[i] = min([res[ix] for x in c if x<=i and res[ix] is not None]) +1 except: res[i] = None # takes care of error when [res[ix] for x in c if x<=i] is empty return res[S]
Try:
print(shortest_seq_length([2, 3], 5)) # 2 print(shortest_seq_length([1, 5, 10, 25], 37)) # 4 print(shortest_seq_length([1, 5, 10], 30)) # 3 print(shortest_seq_length([1, 5, 10], 25)) # 3 print(shortest_seq_length([1, 5, 10], 29)) # 7 print(shortest_seq_length([5, 10], 9)) # None
One of the small improvements of the algorithm is to skip the step of calculating the minimum when the sum is equal to one of the values / coins, but this can be done better if we write a cycle to calculate the minimum. However, the implementation of Thsi does not improve the overall complexity of O(mS)
, where m = len(c)
.