I tried to solve the problem below using dynamic programming, but there is something wrong with my code and I could not figure it out. Could you help me with it? Thank you!
Problem:
Given two arrays of length m and n with digits 0-9 representing two numbers. Create the maximum number of length k <= m + n from digits of the two. The relative order of the digits from the same array must be preserved. Return an array of the k digits.
Note: You should try to optimize your time and space complexity.
Example 1:
Input:
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
k = 5
Output:
[9, 8, 6, 5, 3]
Example 2:
Input:
nums1 = [6, 7]
nums2 = [6, 0, 4]
k = 5
Output:
[6, 7, 6, 0, 4]
Example 3:
Input:
nums1 = [3, 9]
nums2 = [8, 9]
k = 3
Output:
[9, 8, 9]
My idea is as following:
dp[i][j][t] is the the maximum number of length i which is picked out of first j digits of array 1 and first t digits of array 2, where i goes from 0 to k, j goes from 0 to len(nums1), t goes from 0 to len(nums2). the state transition equation goes like this:
when nums1[j-1] > nums2[t-1]:
if we already have k-2 digits, then we have to take both nums1[j-1] and nums2[t-1], and we must take nums1[j-1] first in order to maximize the result
if we already have k-1 digits, then we only have to take one more digit, and it must be nums1[j-1], because it is bigger
if we already have k digits, then we do not need to take more digits, so we keep the last result dp[i][j-1][t-1]
Given that we are looking for maximum, our current result should be the biggest among these 3 situations, so we have:
dp[i][j][t] = max(
(dp[i-2][j-1][t-1]*10+nums1[j-1])*10+nums2[t-1],
dp[i-1][j-1][t-1]*10+nums1[j-1],
dp[i][j-1][t-1]
)
when nums1[j-1] < nums2[t-1]:
if we already have k-2 digits, then we have to take both nums1[j-1] and nums2[t-1], and this time we must take nums2[t-1] first because it is bigger
if we already have k-1 digits, then we only have to take one more digit, and it must be nums2[t-1], because it is bigger
if we already have k digits, then we do not need to take more digits, so we keep the last result dp[i][j-1][t-1]
Likewise, we take the biggest result from these possible ones:
dp[i][j][t] = max(
(dp[i-2][j-1][t-1]*10+nums2[t-1])*10+nums1[j-1],
dp[i-1][j-1][t-1]*10+nums2[t-1],
dp[i][j-1][t-1]
)
Here is my code:
import numpy as np
def maxNumber(nums1, nums2, k):
m = len(nums1)
n = len(nums2)
dp = [[[0 for _ in range(n + 1)] for _ in range(m + 1)] for _ in range(k + 1)]
for i in range(2, k + 1):
for j in range(i + 1):
if j > m or (i - j) > n:
continue
tmp = 0
tmp_nums1 = nums1[:j]
tmp_nums2 = nums2[:(i-j)]
while tmp_nums1 or tmp_nums2:
if tmp_nums1 > tmp_nums2:
tmp = tmp * 10 + tmp_nums1.pop(0)
else:
tmp = tmp * 10 + tmp_nums2.pop(0)
dp[i][j][i - j] = tmp
for i in range(m + 1):
for j in range(n + 1):
if not i and not j:
continue
dp[1][i][j] = max(nums1[:i] + nums2[:j])
for i in range(2, k+1):
for j in range(m+1):
for t in range(i+1-j, n + 1):
if nums1[j - 1] > nums2[t - 1]:
dp[i][j][t] = max((dp[i-2][j-1][t-1]*10+nums1[j-1])*10+nums2[t-1], dp[i][j-1][t-1], dp[i-1][j-1][t-1]*10+nums1[j-1])
else:
dp[i][j][t] = max((dp[i-2][j-1][t-1]*10+nums2[t-1])*10+nums1[j-1], dp[i][j-1][t-1], dp[i-1][j-1][t-1]*10+nums2[t-1])
# print(np.array(dp))
res = []
tmp_res = dp[-1][-1][-1]
while tmp_res:
res.append(tmp_res % 10)
tmp_res //= 10
return res[::-1]
But it outputs [8, 9, 9] on Example 3, and I cannot figure out the reason. Could you help me with it?
Thank you in advance!
Dynamic programming usually implies short-circuiting some of the computation based on results from computations made to date. Often this takes the form of a recursive function. You seem to be taking more of a brute force approach (which usually corresponds to the worse case scenario for dp)
Here is an example of a recursive approach that will lend itself better to optimization:
def largestFrom(M,N,K):
if K == 1: return [max(M+N)] # simple case
if not M and len(N)==K : return N # simple case
if not N and len(M)==K : return M # simple case
result = []
for A,B in [(N,M),(M,N)]:
for i,a in enumerate(A): # trial on numbers from A
if len(A)-i+len(B)<K: break # can't take more from A
if result and a < result[0]: continue # short-circuit
R = [a] + largestFrom(A[i+1:],B,K-1) # recurse with remaining numbers
if R > result: result = R # track best so far
return result
After eliminating the obvious solutions that require no special processing, it goes into a recursive trial/error process that short-circuits the traversal for candidate numbers that won't improve the best result found so far.
The traversal goes through the two lists and attempts to use the number at each position as the first one in the result. It then recurses with the remaining numbers and a size of K-1. So, upon returning from the recursion, a list R is formed of the selected number followed by the largest K-1 sized suffix that can be made with the remaining numbers.
One part of the short circuiting is stopping the loop when the index of the selected number would not leave enough remaining numbers to reach a size of K-1 (i.e. combining the remainder of the current list plus all numbers of the other one).
Another part of short circuiting is comparing the number we are about to try with the first one in the best result. If the candidate number is smaller than the first one in the result, then it would be pointless to go deeper as there is no possibility to form an R list greater than the result we already have.
For example:
combining [3,9] [8,9] with K=3
result starts empty
Going through first list [3,9]
select 3 at position 0
recurse with M=[9] N=[8,9] K=2
will produce R = [3] + [9,8]
R > result, result is now [3,9,8]
select 9 at position 1
recurse with M=[] N=[8,9] K=2
will produce R = [9] + [8,9]
R > result, result is now [9,8,9]
Going through second list [8,9]
select 8 at position 0
8 is smaller than R[0] (9)
short-circuit
select 9 at position 1
recurse with M=[3,9] N=[] K=2
will produce R = [9] + [3,9]
result unchanged (R is < result)
return result [9,8,9]
The for A,B in [(N,M),(M,N)]: loop is merely a shorthand way to avoid duplicating the code for the trial loops on numbers in M and numbers N.
testSet = [ ([3,4,6,5],[9,1,2,5,8,3],5),
([6, 7], [6, 0, 4],5),
([3, 9], [8, 9],3)
]
for M,N,K in testSet:
print(M,N,K,":",largestFrom(M,N,K))
[3, 4, 6, 5] [9, 1, 2, 5, 8, 3] 5 : [9, 8, 6, 5, 3]
[6, 7] [6, 0, 4] 5 : [6, 7, 6, 0, 4]
[3, 9] [8, 9] 3 : [9, 8, 9]
There is alternative way than DP to solve it. Here I've just crafted another solution:
def maxNumber(nums1, nums2, k):
def pick(nums, k):
stack = []
drop = len(nums) - k
for num in nums:
while drop and stack and stack[-1] < num:
stack.pop()
drop -= 1
stack.append(num)
return stack[:k]
def merge(A, B):
ans = []
while A or B:
bigger = A if A > B else B
ans.append(bigger.pop(0))
return ans
return max(merge(pick(nums1, i), pick(nums2, k-i))
for i in range(k+1) if i <= len(nums1) and k-i <= len(nums2))
if __name__ == '__main__':
nums1 = [3, 4, 6, 5]
nums2 = [9, 1, 2, 5, 8, 3]
print(maxNumber(nums1, nums2, 5))
print(maxNumber([3,9],[8,9], 3))
Are those answers to your examples provided by the professor? Because they don't make sense to me. Surely the largest number is one that uses all of the digits available? i.e. the largest value will always mean k=m+n. You can't possibly have a larger answer with k=m+(n-1) for instance. What am I missing?
Example 3:
Input: nums1 = [3, 9]
nums2 = [8, 9]
k = 3
Output: [9, 8, 9]
or - in my world k = 4 / Output: [8, 9, 3, 9]
(Hmm... I guess they were provided. Seems a weird question to me. Sorry - I'm unable to help, but I'll post this anyway in case someone else wonders the same thing I did. To me the hard part would be to actually work out what the largest number would be, using all digits. But even then that's not that hard: Compare positions 1 - use the value from the larger array. Compare position 1 of the non-chosen array with position 2... and so on.)
Related
I've got everything working correctly except this one problem - with the third output it says the max pair sum is 13 due to taking sum of the first and last number. It should be 11 (as 9+2 or 4+7). I can't figure it out how I fix this problem - ending the loop with the first and last position.
Thank you for all advice.
def _sum(num_list):
maxSum = -9999999
for i in range(len(num_list)):
for j in range(i-1, i+1):
if not num_list[i] == num_list[j]:
maxSum = max(maxSum, num_list[i] + num_list[j])
return maxSum
print(_sum([1, 8, 7, 3, 5, 2]))
# correct answer: 15
print(_sum([1, 2, 45, 4, 5, 6, 7, 3, 2]))
# correct answer: 49
print(_sum([9, 2, 3, 4, 7, 1, 2, 3, 4]))
# correct answer: 11
I have tried write some if conditions into for j loop but it ended up with many errors. I'm truly out of ideas and I can't even imagine how it could be right.
in your solution, for first element value of j is [-1, 0] beacuse of this it is taking 4 and adding with 0th index element and making their sum as max value. you need to fix it by starting from index 1. A simple way to do that is below
def _sum(num_list):
maxSum = -9999999
for i in range(1, len(num_list)):
for j in range(i-1, i+1):
if not num_list[i] == num_list[j]:
maxSum = max(maxSum, num_list[i] + num_list[j])
return maxSum
You can use itertools.pairwise for this.
Your code considers every possible pairing of elements from the given array. pairwise only considers consecutive pairs:
from itertools import pairwise
def _sum(num_list):
return sum(max(pairwise(num_list), key=lambda item: item[0]+item[1]))
Output as requested
I was asked to write a recursive function to move all multiples of an integer (k) to the end of the list. This function move_multi(num_list, k) takes a list of integers (num_list) and an integer (k) as parameters and returns None.
Sample run of the code:
nums = [1, 2, 3, 4, 5, 6, 7, 8]
k = 3
move_multi(nums, k)
print(nums)
Expected output:
[1, 2, 4, 5, 7, 8, 3, 6]
Rules:
A helper function may be used.
No loop allowed.
The non-multiples and multiples have to be in the original relative order.
move_multi(num_list, k) must return None and only has 2 parameters.
I have a problem identifying the base case and thus completely clueless to even start working on the question.
My attempt:
def move_multi(num_list, k):
if len(num_list) == 0: # base case
return None
if num_list[0] % k == 0: # if [0] is a multiple of k
num_list.append(num_list.pop(0))
move_multi(num_list[1:], k)
else: # if [0] is not a multiple of k
move_multi(num_list[1:], k)
return None
But after calling the function, the list is still the same as before.
If the order of the elements within each of the two categories must stay the same, then you can use this algorithm:
Have an index go from left to right through the list. Whenever a value is a multiple of k, then pop it out the list and append it to the end. At the same time increase a counter named done that keeps track of how many multiples have been moved like that to the end of the list.
When the index reaches the start of the last section of the list (having done number of values), then the base case is reached.
As you are allowed to create a helper function, you can provide such helper function with the two indexes as arguments; the recursion happens in the nested, helper function:
def move_multi(nums, k):
def recur(i, done):
if i + done >= len(nums):
return
if nums[i] % k == 0:
nums.append(nums.pop(i))
recur(i, done+1)
else:
recur(i+1, done)
recur(0, 0)
nums = [1, 2, 3, 4, 5, 6, 7, 8]
k = 3
move_multi(nums, k)
print(nums)
Output:
[1, 2, 4, 5, 7, 8, 3, 6]
Your attempt
The attempt that you added to your question fails because it uses slicing when passing a list to the recursive call:
num_list[1:]
But then you have no way to get the result back: any mutation through append and pop will now happen on a copy of num_list of which you have no reference once the function returns. You should pass the list itself, and not a sliced copy of it, and pass an additional index.
Minimising the number of moves
To minimise the number of movements in the list, I would have preferred the algorithm below, which however does not maintain the original order of the values within each of the two categories, but it has a better time complexity in the worst case:
Have two indexes in the list move from both ends to the list towards each other. Only move an index if the value it referenced was at the correct side of the list (based on whether it divides by k). If neither of these two indexes can move, swap their values, and then move both of them. When the two indexes cross (= base case) the goal is achieved.
In a recursive fashion this can be as follows:
def move_multi(nums, k, i=0, j=-1): # Two extra arguments with default values
if i - j >= len(nums): # NB: j is always negative
return # all values have been inspected
if nums[j] % k == 0:
move_multi(nums, k, i, j-1)
elif nums[i] % k > 0:
move_multi(nums, k, i+1, j)
else:
nums[i], nums[j] = nums[j], nums[i] # swap
move_multi(nums, k, i+1, j-1)
nums = [1, 2, 3, 4, 5, 6, 7, 8]
k = 3
move_multi(nums, k)
print(nums)
Output:
[1, 2, 8, 4, 5, 7, 6, 3]
You can use next method:
def move_multi(nums, k, idx=0):
if idx >= len(nums):
return nums
if nums[idx] % k:
return [nums.pop(idx)] + move_multi(nums, k, idx)
else:
return move_multi(nums, k, idx + 1)
This returns new list which doesn't fully meet your requirements but I've decided to leave it like it is. It's possible to patch it to rewrite source list by using this function as inner function and nums[:] = move_multi_inner(nums, k).
... which will look like this:
def move_multi(nums, k):
def move_multi_inner(nums, k, idx=0):
if idx >= len(nums):
return nums
if nums[idx] % k:
return [nums.pop(idx)] + move_multi_inner(nums, k, idx)
else:
return move_multi_inner(nums, k, idx + 1)
nums[:] = move_multi_inner(nums, k)
nums = [1, 2, 3, 4, 5, 6, 7, 8]
move_multi(nums, 3)
To be frank with you, I find this exercise a kind of abuse of recursion. You could slice the the array into the first element and the remaining n-1 elements. You then apply recursively the function to the remaining n-1 elements. you have now n-1 elements with the multiples at the end. The first element can either be appended at the start or appended at the end if it's a multiple.
You could split the list into two, recursively call the function on both sublists then merge the two lists by first taking the non multiple elements from both lists and appending them to a new list, then take the multiple elements from both sublists and appending them at the end of the resultant list. Pointless and inefficient, but meets the requirements.
The following is another approach that switches elements:
def move_multi(l, n, idx=0):
if idx == len(l)-1:
if l[idx] % n == 0:
return idx - 1
else:
return idx;
else:
last_non_div = move_multi(l, n, idx+1)
if l[idx] % n == 0:
l[idx], l[last_non_div] = l[last_non_div], l[idx]
return last_non_div - 1
else:
return last_non_div
n = 2
l = [1, 2, 3, 4, 5, 6, 7, 8, 9]
move_multi(l, n)
print(l)
n = 3
move_multi(l, n)
print(l)
l = [2] * 10
move_multi(l, n)
print(l)
l = [1] * 10
move_multi(l, n)
print(l)
n = 2
l = [2, 3, 1, 2, 5, 2, 4]
move_multi(l, n)
print(l)
This one's also working fine, except it is non-recursive.
def multiplier(l1, r):
l2 = []
for i in range(0, r):
if l1[i] % 10 == 0:
l2.append(l1[i])
for j in l2:
if j in l1:
l1.remove(j)
l1.append(j)
return l1
I want to print harmonic series in python as follow but I don't understand how to do it, pls help and it would be nice if you could maybe explain how you did it. I have not tried any code and was not able to find it on the web
The problem
We want to iterate n (1, 2, 3, 4, 5, ...) and pass it in the formula nx/n!, for example user ill define x by this line of code x = int(input("What's the value of x? ")), so image user types number 5, so we need to get: 1*5/1!, 2*5/2!, 3*5/3!, 4*5/4!.
Here is another problem: Python's ! symbol means boolean invert, so !true is equal to false, not factorial.
factorial function in Python
So we need to define function factorial:
def factorial(number):
fact = 1
for n in range(1, number+1):
fact *= n # The same as fact = fact * n
return fact
# TEST OF THE FACTORIAL FUNCTION
# print(f'factorial(3) == 1*2*3 => { factorial(3) == 1*2*3 }')
Limit of the sequence
We actually need to get the nlim number from the user that tells the loop when to stop.
nlim = int(input("What's the limit of the sequence? "))
The sequence
So, we need to get Python evaluate this (if x is equal to 5 and n is increasing from 1 by step 1 to limit nlim): 1*5/factorial(1), 2*5/factorial(2), 3*5/factorial(3) and so on.
results = [] # in this list all the results will be stored
for n in range(1, nlim+1):
results.append((n*x) / factorial(n)) # here comes the formula!
Read sequence
for i, result in enumerate(results):
# enumerate([7, 8, 9]) will give us 2D list with indexes [[0, 7], [1, 8], [2, 9]]
# Then we will iterate this, so on each loop cycle we get [0, 7], then [1, 8] and so on
# if we do following: a, b = [1, 2] then variable a will be 1 and variable b will be 2
print(f'result[{ i }]: { result }')
All the code
def factorial(number):
fact = 1
for n in range(1, number+1):
fact *= n
return fact
x = int(input("What's the value of x? "))
nlim = int(input("What's the limit of the sequence? "))
results = []
for n in range(1, nlim+1):
results.append((n*x) / factorial(n))
for i, result in enumerate(results):
print(f'result[{ i }]: { result }')
I'm trying to look for the number of combinations of 7 digit numbers (or more, actually need it to work for 10, but its faster to test with 7) that have 1,3,5,7 in it. Tried a few different methods like using
combinations = 0
for combination in itertools.product(xrange(10), repeat=7):
if all(x in combination for x in (1,3,5,7)):
combinations += 1
However, this next method worked out to be about 4 times faster as it doesnt look for 3,5,7 if 1 is not in the list.
combinations = 0
for combination in itertools.product(xrange(10), repeat=7):
if 1 in combination:
if 3 in combination:
if 5 in combination:
if 7 in combination:
combinations += 1
I'm sure there is a more cleaver way to achieve this result with numpy or something like that, but I can't figure it out.
Thanks for feedback
The problem is to find k-digit numbers that contain all the digits 1, 3, 5, 7.
This answer contains a number of solutions, increasing in sophistication and algorithmic efficiency. By the end, we'll be able to, in a fraction of a second, count solutions for huge k, for example 10^12, modulo a large prime.
The section at the end includes tests that provide good evidence that all the implementations are correct.
Brute force: O(k10^k) time, O(k) space
We'll use this slow approach to test the more optimized versions of the code:
def contains_1357(i):
i = str(i)
return all(x in i for x in '1357')
def combos_slow(k):
return sum(contains_1357(i) for i in xrange(10 ** k))
Counting: O(k^4) time, O(k) space
The simplest moderately efficient method is to count. One way to do this is to count all k-digit numbers where the first occurrences of the four special digits appear at digits a, b, c, d.
Given such an a, b, c, d, the digits up to a must be 0,2,4,6,8,9, the digit a must be one of [1, 3, 5, 7], the digits between a and b must be either the same as the digit a or any of the safe digits, the digit b must be one of [1, 3, 5, 7] that's different from the digit at a, and so on.
Summing over all possible a, b, c, d gives the result. Like this:
import itertools
def combos0(k):
S = 0
for a, b, c, d in itertools.combinations(range(k), 4):
S += 6 ** a * 4 * 7**(b-a-1) * 3 * 8**(c-b-1) * 2 * 9**(d-c-1) * 10**(k-d-1)
return S
Dynamic programming: O(k) time, O(k) and then O(1) space
You can solve this more efficiently with dynamic programming: let c[j][i] be the number of i-digit numbers which contain exactly j different digits from (1, 3, 5, 7).
Then c satisfies these recurrence relations:
c[0][0] = 1
c[j][0] = 0 for j > 0
c[0][i] = 6 * c[0][i-1] for i > 0
c[j][i] = (6+j)c[j][i-1] + (5-j)c[j-1][i-1] for i, j > 0
The final line of the recurrence relations is the hardest one to understand. The first part (6+j)c[j][i-1] says that you can make an i digit number containing j of the digits 1, 3, 5, 7 from a i-1 digit number containing j of the digits 1, 3, 5, 7, and add an extra digit that's either 0, 2, 4, 6, 8, 9 or any of the digits you've already got. Similarly, the second part (5-j)c[j-1][i-1] says that you can take an i-1 digit number containing j-1 of the digits 1, 3, 5, 7 and make it an i-digit number containing j of the special digits by adding one of the digits you haven't already used. There's 5-j of these.
That leads to this O(k) solution using dynamic programming:
def combos(k):
c = [[0] * (k + 1) for _ in xrange(5)]
c[0][0] = 1
for i in xrange(1, k+1):
c[0][i] = 6 * c[0][i-1]
for j in xrange(1, 5):
c[j][i] = (6 + j) * c[j][i-1] + (5-j) * c[j-1][i-1]
return c[4][k]
We can print combos(10):
print 'combos(10) =', combos(10)
This gives this output:
combos(10) = 1425878520
The solution above is already fast enough to compute combos(10000) in a fraction of a second. But it's possible to optimize the DP solution a little to use O(1) rather than O(k) space by observing that values of c depend only on the previous column in the table. With a bit of care (to make sure that we're not overwriting values before they're used), we can write the code like this:
def combos2(k):
c = [1, 0, 0, 0, 0]
for _ in xrange(k):
for j in xrange(4, 0, -1):
c[j] = (6+j)*c[j] + (5-j)*c[j-1]
c[0] *= 6
return c[4]
Matrix power: O(log k) time, O(1) space.
Ultimately, it's possible to get the result in O(log k) time and O(1) space, by expressing the recurrence relation as a matrix-by-vector multiply, and using exponentiation by squaring. That makes it possible to compute combos(k) modulo X even for massive k (here combos(10^12) modulo 2^31 - 1). That looks like this:
def mat_vec(M, v, X):
return [sum(M[i][j] * v[j] % X for j in xrange(5)) for i in xrange(5)]
def mat_mul(M, N, X):
return [[sum(M[i][j] * N[j][k] for j in xrange(5)) % X for k in xrange(5)] for i in xrange(5)]
def mat_pow(M, k, X):
r = [[i==j for i in xrange(5)] for j in xrange(5)]
while k:
if k % 2:
r = mat_mul(r, M, X)
M = mat_mul(M, M, X)
k //= 2
return r
def combos3(k, X):
M = [[6, 0, 0, 0, 0], [4, 7, 0, 0, 0], [0, 3, 8, 0, 0], [0, 0, 2, 9, 0], [0, 0, 0, 1, 10]]
return mat_vec(mat_pow(M, k, X), [1, 0, 0, 0, 0], X)[4]
print combos3(10**12, (2**31) - 1)
Given that your original code struggled for k=10, this is quite an improvement!
Testing
We can test each of the functions against each other (and combos_slow for small values). Since combos3 has an extra arg, we wrap it in a function that passes a modulo that's guaranteed to be larger than the result.
def combos3p(k):
return combos3(k, 10**k)
for c in [combos0, combos, combos2, combos3p]:
for i in xrange(40 if c == combos0 else 100):
assert c(i) == (combos_slow if i < 7 else combos)(i)
This tests all the implementations against combos_slow for i<7, and against each other for 7 <= i < 100 (except for the less efficient combos0 which stops at 40).
I'm totally stuck and have no idea how to go about solving this. Let's say I've an array
arr = [1, 4, 5, 10]
and a number
n = 8
I need shortest sequence from within arr which equals n. So for example following sequences within arr equals n
c1 = 5,1,1,1
c2 = 4,4
c3= 1,1,1,1,1,1,1,1
So in above case, our answer is c2 because it's shortest sequences in arr that equals sum.
I'm not sure what's the simplest way of finding a solution to above? Any ideas, or help will be really appreciated.
Thanks!
Edited:
Fixed the array
Array will possibly have postive values only.
I'm not sure how subset problem fixes this, probably due to my own ignorance. Does sub-set algorithm always give the shortest sequence that equals sum? For example, will subset problem identify c2 as the answer in above scenario?
As has been pointed before this is the minimum change coin problem, typically solved with dynamic programming. Here's a Python implementation solved in time complexity O(nC) and space complexity O(C), where n is the number of coins and C the required amount of money:
def min_change(V, C):
table, solution = min_change_table(V, C)
num_coins, coins = table[-1], []
if num_coins == float('inf'):
return []
while C > 0:
coins.append(V[solution[C]])
C -= V[solution[C]]
return coins
def min_change_table(V, C):
m, n = C+1, len(V)
table, solution = [0] * m, [0] * m
for i in xrange(1, m):
minNum, minIdx = float('inf'), -1
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 (table, solution)
In the above functions V is the list of possible coins and C the required amount of money. Now when you call the min_change function the output is as expected:
min_change([1,4,5,10], 8)
> [4, 4]
For the benefit of people who find this question in future -
As Oscar Lopez and Priyank Bhatnagar, have pointed out, this is the coin change (change-giving, change-making) problem.
In general, the dynamic programming solution they have proposed is the optimal solution - both in terms of (provably!) always producing the required sum using the fewest items, and in terms of execution speed. If your basis numbers are arbitrary, then use the dynamic programming solution.
If your basis numbers are "nice", however, a simpler greedy algorithm will do.
For example, the Australian currency system uses denominations of $100, $50, $20, $10, $5, $2, $1, $0.50, $0.20, $0.10, $0.05. Optimal change can be given for any amount by repeatedly giving the largest unit of change possible until the remaining amount is zero (or less than five cents.)
Here's an instructive implementation of the greedy algorithm, illustrating the concept.
def greedy_give_change (denominations, amount):
# Sort from largest to smallest
denominations = sorted(denominations, reverse=True)
# number of each note/coin given
change_given = list()
for d in denominations:
while amount > d:
change_given.append(d)
amount -= d
return change_given
australian_coins = [100, 50, 20, 10, 5, 2, 1, 0.50, 0.20, 0.10, 0.05]
change = greedy_give_change(australian_coins, 313.37)
print (change) # [100, 100, 100, 10, 2, 1, 0.2, 0.1, 0.05]
print (sum(change)) # 313.35
For the specific example in the original post (denominations = [1, 4, 5, 10] and amount = 8) the greedy solution is not optimal - it will give [5, 1, 1, 1]. But the greedy solution is much faster and simpler than the dynamic programming solution, so if you can use it, you should!
This is problem is known as Minimum coin change problem.
You can solve it by using dynamic programming.
Here is the pseudo code :
Set MinCoin[i] equal to Infinity for all of i
MinCoin[0] = 0
For i = 1 to N // The number N
For j = 0 to M - 1 // M denominations given
// Number i is broken into i-Value[j] for which we already know the answer
// And we update if it gives us lesser value than previous known.
If (Value[j] <= i and MinCoin[i-Value[j]]+1 < MinCoin[i])
MinCoin[i] = MinCoin[i-Value[j]]+1
Output MinCoin[N]
This is an variant of subset-sum problem. In your problem, you can pick an item several times. You still can use a similar idea to solve this problem by using the dynamic prorgamming technique. The basic idea is to design a function F(k, j), such that F(k, j) = 1 means that there is a sequence from arr whose sum is j and length is k.
Formally, the base case is that F(k, 1) = 1, if there exists an i, such that arr[i] = k. For inductive case, F(k, j) = 1, if there exists an i, such that arr[i] = m, and F(k-1, j-m) = 1.
The smallest k with F(k, n) = 1 is the length of the shortest sequence you want.
By using the dynamic programming technique, you can compute function F without using recursion.
By tracking additional information for every F(k, j), you also can reconstruct the shortest sequence.
What you're trying to solve is a variant of the coin change problem. Here you're looking for smallest amount of change, or the minimum amount of coins that sum up to a given amount.
Consider a simple case where your array is
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 the set of coin values and 5 is the amount for which you want to get change.
Let's write down all possible combinations:
1 + 1 + 1 + 1 + 1
1 + 1 + 1 + 2
1 + 2 + 2
1 + 1 + 3
2 + 3
Note that two combinations are the same up to re-ordering, so for instance 2 + 3 = 3 + 2.
Here there is an awesome result that's not obvious at first sight but it's very easy to prove. If you have any sequence of coins/values that is a sequence of minimum length that sums up to a given amount, no matter how you split this sequence the two parts will also be sequences of minimum length for the respective amounts.
For instance if c[3] + c[1] + c[2] + c[7] + c[2] + c[3] add up to S and we know that 6 is the minimal length of any sequence of elements from c that add 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 minimal length for sequences that add up to c[3] + c[1] + c[2] + c[7] and 2 the minimal 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 this? By contradiction, assume that the length of S_left is not optimal, that is there's a shorter sequence that adds up to S_left. But then we could write S as a sum of this shorter sequence and S_right, thus contradicting the fact that the length of S is minimal. □
Since this is true no matter how you split the sequence, you can use this result to build a recursive algorithm that follows the principles of dynamic programming paradigm (solving smaller problems while possibly skipping computations that won't be used, memoization or keeping track of computed values, and finally combining the results).
Because of this property of maintaining optimality for subproblems, the coins problem is also said to "exhibit optimal substructure".
OK, so in the small example above this is how we would go about solving the problem with a dynamic programming approach: assume we want to find the shortest sequence of elements from c = [1, 2, 3] for writing the sum 5. We solve the subproblems obtained by subtracting one coin: 5 - 1, 5 - 2, and 5 - 3, we take the smallest solution of these subproblems 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 bottom-up, starting from smaller values of the sums that can be saved and used to form bigger sums. We just solve the problem for all possible values starting from 1 and going up to the desired sum.
Here's the code in Python:
def shortest_seq_length(c, S):
res = {0: 0} # res contains computed results res[i] = shortest_seq_length(c, i)
for i in range(1, S+1):
res[i] = min([res[i-x] for x in c if x<=i]) + 1
return res[S]
Now this works except for the cases when we cannot fill the memoization structure for all values of i. This is the case when we don't have the value 1 in c, so for instance we cannot form the sum 1 if c = [2, 5] and with the above function we get
shortest_seq_length([2, 3], 5)
# ValueError: min() arg is an empty sequence
So to take care of this issue one could for instance use a try/catch:
def shortest_seq_length(c, S):
res = {0: 0} # res contains results for each sum res[i] = shortest_seq_length(c, i)
for i in range(1, S+1):
try:
res[i] = min([res[i-x] for x in c if x<=i and res[i-x] is not None]) +1
except:
res[i] = None # takes care of error when [res[i-x] for x in c if x<=i] is empty
return res[S]
Or without try/catch:
def shortest_seq_length(c, S):
res = {0: 0} # res[i] = shortest_seq_length(c, i)
for i in range(1, S+1):
prev = [res[i-x] for x in c if x<=i and res[i-x] is not None]
if len(prev)>0:
res[i] = min(prev) +1
else:
res[i] = None # takes care of error when [res[i-x] for x in c if x<=i] is empty
return res[S]
Try it out:
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
To show not only the length but also the combinations of coins of minimal length:
from collections import defaultdict
def shortest_seq_length(coins, sum):
combos = defaultdict(list)
combos[0] = [[]]
for i in range(1, sum+1):
for x in coins:
if x<=i and combos[i-x] is not None:
for p in combos[i-x]:
comb = sorted(p + [x])
if comb not in combos[i]:
combos[i].append(comb)
if len(combos[i])>0:
m = (min(map(len,combos[i])))
combos[i] = [combo for i, combo in enumerate(combos[i]) if len(combo) == m]
else:
combos[i] = None
return combos[sum]
total = 9
coin_sizes = [10, 8, 5, 4, 1]
shortest_seq_length(coin_sizes, total)
# [[1, 8], [4, 5]]
To show all sequences remove the minumum computation:
from collections import defaultdict
def all_seq_length(coins, sum):
combos = defaultdict(list)
combos[0] = [[]]
for i in range(1, sum+1):
for x in coins:
if x<=i and combos[i-x] is not None:
for p in combos[i-x]:
comb = sorted(p + [x])
if comb not in combos[i]:
combos[i].append(comb)
if len(combos[i])==0:
combos[i] = None
return combos[sum]
total = 9
coin_sizes = [10, 5, 4, 8, 1]
all_seq_length(coin_sizes, total)
# [[4, 5],
# [1, 1, 1, 1, 5],
# [1, 4, 4],
# [1, 1, 1, 1, 1, 4],
# [1, 8],
# [1, 1, 1, 1, 1, 1, 1, 1, 1]]
One small improvement to the algorithm is to skip the step of computing the minimum when the sum is equal to one of the values/coins, but this can be done better if we write a loop to compute the minimum. This however doesn't improve the overall complexity that's O(mS) where m = len(c).