面试题 08.11. 问题描述
硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)
解题思路1.错误思路,首先想到的就是回溯法,画出解法空间
对于n=25找零方式可以分解为四个解空间,然后再对子空间进行分解,很容易用递归完成。
代码如下
class Solution:
def __init__(self):
self.num = 0 #总次数
def waysToChange(self, n: int) -> int:
def res(n):
if n==0:#每次当n为0时候,即表示到了解空间的叶子节点,这条路径即为一种解法
self.num+=1
for i in [1,5,10,15]: #对每一层都进行回溯过程
if n>=i:
self.waysToChange(n-i)
return self.num%1000000007
run完之后发现结果不对,这里是不同解做了一个类似去全排列的过程,假如25分解,上面的做法会出现[5,10,5,5,5],[10,5,5,5,5]这些解法,仅仅是位置不同而已,所以如果对最后的结果进行一个排序去重的话,可以算出正确的找零方式,目前还没什么好的思路,估计如果能实现的话,估计复杂度也过不了。
正确思路,动态规划:本题有两个变量硬币的种类数k,找零的总数v,问题可描述为对(k,v)求解,表示为f(k,v)
冷静思考一下,考虑怎么分解为子问题,很容易想到,如果种类数k少一种ki的话,是怎样的一种对应关系,是不是对应的是去掉所有包含ki这一硬币,ki是不是有可能是0个,对应f(k-1,coins[ki]*0)这么多种情况;也有可能是1一个,对应f(k-1,coins[ki]*1)这么多种情况;ki最多的个数为v整除coins[ki],假设为n个,即可得到状态转移方式为:
f
(
k
,
v
)
=
f
(
k
−
1
,
v
−
0
∗
c
o
i
n
s
[
k
i
]
)
+
f
(
k
−
1
,
v
−
1
∗
c
o
i
n
s
[
k
i
]
)
+
.
.
.
f
(
k
−
1
,
v
−
n
∗
c
o
i
n
s
[
k
i
]
)
f(k,v) = f(k-1,v-0*coins[ki])+f(k-1,v-1*coins[ki])+...f(k-1,v-n*coins[ki])
f(k,v)=f(k−1,v−0∗coins[ki])+f(k−1,v−1∗coins[ki])+...f(k−1,v−n∗coins[ki])
找到了状态转移矩阵案例说用一个二维dp,然后双重循环就可以解决该问题了,但是对于每一个状态,里面还需要对n进行一个小循环,所以应该采用三种循环来解决。
时间复杂度优化 通过找规律很容易得到:
f
(
k
,
v
−
c
o
i
n
s
[
k
i
]
)
=
f
(
k
−
1
,
v
−
1
∗
c
o
i
n
s
[
k
i
]
)
+
f
(
k
−
1
,
v
−
2
∗
c
o
i
n
s
[
k
i
]
)
+
.
.
.
f
(
k
−
1
,
v
−
n
∗
c
o
i
n
s
[
k
i
]
)
f(k,v-coins[ki]) = f(k-1,v-1*coins[ki])+f(k-1,v-2*coins[ki])+...f(k-1,v-n*coins[ki])
f(k,v−coins[ki])=f(k−1,v−1∗coins[ki])+f(k−1,v−2∗coins[ki])+...f(k−1,v−n∗coins[ki])
原式可以写为:
f
(
k
,
v
)
=
f
(
k
−
1
,
v
)
+
f
(
k
,
v
−
c
o
i
n
s
[
k
i
]
)
f(k,v)= f (k-1,v)+f(k,v-coins[ki])
f(k,v)=f(k−1,v)+f(k,v−coins[ki])
这可以解决里层n的循环,用两层循环表示状态转移矩阵
空间复杂度优化原本需要4(v+1)大小数组来保存所有的状态,通过观察可知k,v只和k-1,v和k,v-coins[ki]
的大小相关,k,v-coins[ki]在前面已知,而且在第k列中,而k-1,v我们可以作为临时值不保存也可以,只需在前面数组寻找v-coins位置的值,与其相加更新k值即可。
代码如下:
class Solution:
def waysToChange(self, n: int) -> int:
coins = [1, 5, 10, 25]
dp = [1]+[0]*n
for j in coins:
for i in range(j,n+1):
dp[i]+=dp[i-j]
return dp[-1]%1000000007
代码非常简单,如果空间复杂度绕不过来,建议还是用二维dp数组来保存每一个状态,简答粗暴。