算法题:换钱的方法数
最近在看左程云的《程序员代码面试指南》,感觉不错,题都分了类,很方便有目的的刷题,书里的代码都是java实现的,刚好最近在学习python,就用python去练习一下。
1. 问题描述
给定数组arr,其value代表货币面额,货币可无限张使用,给定一个整数aim作为要找的钱数,求组成aim的方法数。
举例:
arr = [5, 10, 25, 1]
有6种方法,分别为 [3, 0, 0, 0], [1, 1, 0, 0], [0, 1, 0, 5], [1, 0, 0, 10], [2, 0, 0, 5], [0, 0, 0, 15]
返回6
2.解决方法
1)暴力递归:从arr[0]开始,尝试每一种面值不同张数。具体看源码
2)动态规划:首先确定了本题尝试是无后效性的,即现状态的返回值与怎么到达这个状态无关。然后确定可变参数:cur 和 rest,cur代表钱币面值数组位置,rest代表要找钱数,与01背包问题有一点类似,从下往上,从左往右的构造表(也可以从上往下,主要取决于对m[i][j]的定义,我喜欢定义m[i][j]为选择i~N,rest=j时的结果)。初始化这张表后,把表中数据计算完整。递归关系不易看出,但画出图后分析可以得出m[i][j]依赖于m[i+1]几乎一整行的数据,再加以判断,可以发现,其实m[i][j]与同一行的数据也有一定联系:m[i][j]就是m[i][j - arr[i]] 和 m[i+1][j] 的相加之和
3.代码实现
def minForm(arr, p, aim):
if p == len(arr):
return 1 if aim == 0 else 0
res = 0
i = 0
while arr[p]*i <= aim:
res += minForm(arr, p+1, aim-arr[p]*i)
i += 1
return res
def minForms(arr, aim):
m = [[0 for i in range(aim+1)] for j in range(len(arr))]
j = len(arr)
i = 0
while arr[j-1]*i <= aim:
m[j-1][arr[j-1]*i] = 1
i += 1
for i in range(j):
m[i][0] = 1
i = len(arr)-2
while i >= 0:
for j in range(1,aim+1):
if j - arr[i] >= 0:
m[i][j] += m[i][j-arr[i]]
if i + 1 < len(arr):
m[i][j] += m[i+1][j]
i -= 1
print('m表:')
for item in m:
print(item)
return m[0][aim]
if __name__ == "__main__":
arr = [5, 10, 25, 1]
aim = 15
print('暴力递归方法结果:{0}'.format(minForm(arr, 0, aim)))
print('动态规划方法结果:{0}'.format(minForms(arr, aim)))
暴力递归方法结果:6
m表:
[1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
动态规划方法结果:6