问题描述:
给你六种面额1
、5
、10
、20
、50
、100
元的纸币,假设每种币值的数量都足够多,编写程序求组成N
元(N
为0-10000
的非负整数)的不同组合的个数。
输入描述: 输入为一个数字N,即需要拼凑的面额
输出描述: 输出也是一个数字,为组成N的组合个数。
示例
输入:5
输出:2
问题分析:
经典题目,类似于背包问题或者上台阶问题(上台阶问题只有两种情况,而这个有几种面额零钱就有多少种情况),昨天晚上58到家的现场宣讲会笔试题,其实牛客网上也有的,链接已经给出。现在总结一下,动态规划解决,具体思路如下:
(1)设 dp[n,m]
表示 N
元钱,且有零钱M
种时,不同组合数。并且,把零钱放到nums=[1, 5, 10, 20, 50, 100]
。(后面的m
,都可以理解为第m
种零钱,或者当前有m
种零钱即可)
(2)现在思考 dp[n,m]
怎么求?很容易联想到上台阶问题,要想得到dp[n,m]
的值,一定是从
∑
j
=
0
m
d
p
[
n
−
n
u
m
s
[
j
]
,
m
]
+
d
p
[
n
,
m
−
1
]
\sum _{j=0} ^{m}{dp[n-nums[j], m]}+dp[n, m-1]
∑j=0mdp[n−nums[j],m]+dp[n,m−1] 得到的,具体点:
A、当m
相等时,当前状态一定是前m
种状态的总和,即,
∑
j
=
0
m
d
p
[
n
−
n
u
m
s
[
j
]
,
m
]
\sum _{j=0} ^{m}{dp[n-nums[j], m]}
∑j=0mdp[n−nums[j],m] 。
B、当n
相等时,很显然当前有m
种零钱,它情况一定包含m-1
种零钱的所有情况,即,
d
p
[
n
,
m
−
1
]
dp[n, m-1]
dp[n,m−1]
C、最后 dp[n,m]
就是这两种情况的和。
要注意的是n-nums[m]>=0
是前提条件。所以得出状态转移方程为:
1. n >=nums[j]
时:
d
p
[
n
,
m
]
=
d
p
[
n
,
m
−
1
]
+
∑
j
=
0
m
d
p
[
n
−
n
u
m
s
[
j
]
,
m
]
dp[n,m]=dp[n, m-1]+\sum _{j=0} ^{m}{dp[n-nums[j], m]}
dp[n,m]=dp[n,m−1]+∑j=0mdp[n−nums[j],m]
2. n < nums[j]
时:
d
p
[
n
,
m
]
=
d
p
[
n
,
m
−
1
]
dp[n,m]=dp[n, m-1]
dp[n,m]=dp[n,m−1]
3. 此外就边界情况,均为1
(3)dp
空间压缩,先看一下下面得图片:
dp[10,2] = dp[10,1] + dp[0, 2] = 3 + 1 = 4
在结合上面的公式不难发现,当前值,只和上面一行的值(情况B),还有本行的值(情况A)有关,所以,现在可以压缩一下dp
空间,即,一行一行的计算。
Python3实现:
# @Time :2018/10/12
# @Author :LiuYinxing
# 动态规划 - 类似于背包问题、上台阶问题
class Solution:
def solve(self, n):
nums = [1, 5, 10, 20, 50, 100] # 基本面额
dp = [1] * (n + 1) # 初始化dp
for j in range(1, 6):
for i in range(1, n+1):
if i >= nums[j]: # 约束条件
dp[i] += dp[i - nums[j]]
return dp[-1]
def solve1(self, n):
nums = [1, 5, 10, 20, 50, 100] # 基本面额
dp = [0] * (n + 1) # 初始化dp
dp[0] = 1 # 初始化dp
for j in range(6):
for i in range(nums[j], n+1):
# if i >= nums[j]: # 约束条件
dp[i] += dp[i - nums[j]]
return dp[-1]
if __name__ == '__main__':
solu = Solution()
print(solu.solve(n=7))
声明: 总结学习,有问题或不妥之处,可以批评指正哦。
[1] 题目/参考-链接:www.nowcoder.com/questionTerminal/14cf13771cd840849a402b848b5c1c93