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-1
coinsdp_sum[i-coins[j]][j]
, number of options, where we removed coin numberj
and 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 1
st column, not 0
th.
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^4
and1+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 is4
possible ways to get degree5
. - Note, that if all coefficients are less than some big number
N
, sayN=1000
for 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^32
into 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