问题描述https://leetcode-cn.com/problems/coin-lcci/
硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)
等价
背包。给定数量物品,物品体积为25、10、5和1,编写代码背包体积为n,装物品的方法有多少种。(结果可能会很大,你需要将结果模上1000000007)
关键点
1.定义dp数组,dp[i][j]表示前i个物品(本例是硬币),在背包大小(本例是分数)为j的情况下有多少种组合,dp[m][n]就是要求的结果。
2.初始化
前m个硬币,组成0分的种类都是1;前0个硬币,组成非0分的种类都是0. 即dp[0][0] =dp[1][0]...=dp[m][0] =1;dp[0][1]=dp[0][2]...=dp[0][n] =0
3.求解过程。
前一个硬币,组成分数大小为1到n, 种类分别为dp[1][0], dp[1][1], ... , dp[1][n]
前两个硬币,组成分数大小为1到n, 种类分别为dp[2][0], dp[2][1], ... , dp[2][n]
...
前i个硬币,组成分数大小为1到n, 最大价值分别为dp[i][0], dp[i][1], ... , dp[i][n]
4.递推公式. 求dp[i][j]共有两种情况, 装入第i个硬币或者不装入第i个硬币。
装入第i个硬币,剩下的分数为j-coins[i-1], 即从前i个硬币里面组合j-coins[i-1]分,对应的种类等价dp[i][j - coin[i - 1]];
不装入第i个硬币,剩下的分数不变,即从前i-1个硬币里面组合j分,对应的种类dp[i - 1][j];
即递推公式为dp[i][j] = dp[i-1][j] + dp[i][j - coins[j-coins[i - 1]]]
class Solution(object):
def waysToChange(self, n):
"""
:type n: int
:rtype: int
"""
coins = [1, 5, 10, 25]
dp = [[0] * (n + 1) for i in range(len(coins) + 1)]
dp[0][0] = 1
for i in range(1, len(coins) + 1):
dp[i][0] = 1
for j in range(1, n + 1):
dp[i][j] = dp[i - 1][j]
if j >= coins[i - 1]:
dp[i][j] += dp[i][j - coins[i - 1]]
return dp[-1][-1]
def test():
s = Solution()
print s.waysToChange(5) == 2
print s.waysToChange(10) == 4
if __name__ == '__main__':
test()
dp空间优化分析1
分析递推公式,dp[i][j] = dp[i-1][j] + dp[i][j-coins[i - 1]], 可以看出,在求解dp[i][j]时,不需要保存前i-1行,只需要保存第i行(因为要用到dp[i][j-coins[i - 1]]),和第i-1行的第j个元素(因为要用到dp[i-1][j])。 如果只用一行表示,代码如下。
class Solution(object):
def waysToChange(self, n):
"""
:type n: int
:rtype: int
"""
coins = [1, 5, 10, 25]
dp = [0] * (n + 1)
dp[0] = 1
for i in range(1, len(coins) + 1):
for j in range(1, n + 1):
if j >= coins[i - 1]:
dp[j] += dp[j - coins[i - 1]]
return dp[-1] % 1000000007
def test():
s = Solution()
print s.waysToChange(5) == 2
print s.waysToChange(10) == 4
if __name__ == '__main__':
test()
和之前代码相比,少了dp[i][j] = dp[i - 1][j]。因为在遍历到第i行第j列时,还没更新dp[j]时,dp[j]其实是在遍历第i-1行第j列时算出的,相当于前面的dp[i-1][j]。正因为没有覆盖dp[i-1][j]信息,所以dp才可以用一维数组替换二维数组