dp
dfs
generating functions
knapsack
]
Leetcode 0518. Coin Change 2
https://leetcode.com/problems/coin-change-2
Let dp_sum[i][j] be a number of ways to represent amount i such that we use only first j coins. We initialize the first column of this table with 1, because we can say there is one way to get amount = 0, using first j coins: do not take any coins.
To find dp_sum[i][j] we need to look at the last coin taken, it consists of two terms:
dp_sum[i][j-1], number of options, where we take only firstj-1coinsdp_sum[i-coins[j]][j], number of options, where we removed coin numberjand we need to find number of options for the rest amount.
Example: let us consider coins = [1,2,5] and amont = 5. Then table dp_sum will be equal to
| 0 | 1 | 2 | 3 | 4 | 5 | |
|---|---|---|---|---|---|---|
| coin #0 (1) | 1 | 1 | 1 | 1 | 1 | 1 |
| coin #1 (2) | 1 | 1 | 2 | 2 | 3 | 3 |
| coin #2 (5) | 1 | 1 | 2 | 2 | 3 | 4 |
Complexity time and space is O(amount *N), where N is number of different coins, because we need only O(1) to update each cell.
In code I use index i+1 instead of i, because we start from 1st column, not 0th.
Update Space complexity can be reduced to O(amount), because for every j we look at most one row back.
class Solution:
def change(self, amount, coins):
N = len(coins)
if N == 0: return int(N == amount)
dp_sum = [[0] * N for _ in range(amount + 1)]
for i in range(N): dp_sum[0][i] = 1
for i,j in product(range(amount), range(N)):
dp_sum[i+1][j] = dp_sum[i+1][j-1]
if i+1 - coins[j] >= 0:
dp_sum[i+1][j] += dp_sum[i+1-coins[j]][j]
return dp_sum[-1][-1]
Solution 2
This is classical problem for generation functions: you can see this example on wikipedia: number of ways to make change (https://en.wikipedia.org/wiki/Examples_of_generating_functions).
Howerer the main difficulty here is to make it work in time. What I did is I choose x = 2^32, spend like 4 hours, trying different ways to multiply polynomials, using convolutions and optimizing code to avoid TLE and here is the result.
Update:
Let us go through example target = 5 and coins = [1, 2, 5]
- We build 3 generating polynoms:
1+x+x^2+x^3+x^4+x^5,1+x^2+x^4and1+x^5. What we need to do now is to multiply these polynomials and evaluate coefficient before term with degreetarget. Indeed:(1+x+x^2+x^3+x^4+x^5)*(1+x^2+x^4)*(1+x^5) = ... + x*x^4*1 + 1*1*x^5 + x^3*x^2*1 + x^5*1*1, there is4possible ways to get degree5. - Note, that if all coefficients are less than some big number
N, sayN=1000for example andpolynom = 13x^2 + 6x + 3, thanpolynom(1000) = 13006003, so we can easily distinguish coefficients for each degree, if we look at our number as number in base1000. - To make it faster we use the fact
(1+x+x^2+x^3+x^4+x^5) = (x^6-1)/(x-1),(1+x^2+x^4) = (x^6-1)/(x^2-1)and1+x^5=(x^10-1)/(x^5-1). - We take
N=2^32, because it is given in statement that we can never get numbers more thanN. - Substitute
N=2^32into our polynom and evaluate it! Python allows as to do computations of big numbers. - Optimisations: use a lot of bit manipulations to avoid TLE.
class Solution:
def change(self, amount, coins):
S = [(((1<<(32*(amount//i*i + i)))) - 1)//((1<<(i*32)) - 1) for i in coins]
prodd = 1
for i in S: prodd = (prodd * i) & ((1<<((amount +1)*32))-1)
return prodd>>(32*amount)
We can write it as Oneliners in the following ways - thanks https://leetcode.com/joric/ for this!
return reduce(lambda p,i:(p*i)&(1<<((amount+1)*32))-1,[((1<<(32*(amount//i*i+i)))-1)//((1<<(i*32))-1) for i in coins],1)>>(32*amount)
return list(accumulate(coins,lambda p,i:(p*(((1<<(32*(amount//i*i+i)))-1)//((1<<(i*32))-1)))&((1<<((amount+1)*32))-1),initial=1))[-1]>>(32*amount)
If you have any questions, feel free to ask. If you like solution and explanations, please Upvote!
If you like the solution, you can upvote it on leetcode discussion section: Problem 0518