动态规划算法学习(一)爬楼梯和凑金额

爬楼梯
每步可跨台阶:1,2,3 —该场景表示有序组合
台阶总数:15
求多少种跨法

'''
倒推下,当我倒数第二步已经走了14、13或12步时,我最后一步就可以就可以走完15个台阶
    此时我15步的走法跟最后一步无关,而是跟前面的三种结果有关,即是12+13+14步的走法和
由此再分别倒推14/13/12三种情况的走法,得到是13+12+11和12+11+10和11+10+9九种情况的走法和
....
在分解到最后,你就会发现是多个1/2/3走法的和
以F表示走法的函数,i表示台阶总数,F(i-1)、F(i-2)、F(i-3)分别表示最后一步为1/2/3的情况
即 F(i) = F(i-1)+F(i-2)+F(i-3)
我们建立一个数组,以下标表示总台阶数,则长度为n+1,再在每一个坐标元素上设置值为对应的总走法数
即有 [0,1,2,4,
'''
n = 15
steps = [1, 2, 3]
list_a = [0] * (n + 1)
list_a[0] = 1  # 总台阶数为0时0种走法,但是为了后面好进行迭代,我们设置为1,
list_a[1] = 1  # 总台阶数为1时只有[{1}]这一种走法,使用公式应为 f[0]的走法与最后一步为1的走法的组合
list_a[2] = 2  # 总台阶数为2时有[{1,1},{2}]这2种走法,使用公式应为 f[1]的走法与最后一步为1的走法的组合加上f[0]与最后一步为2的走法的组合
list_a[3] = 4  # [{1,1,1},{1,2},{2,1},{3}]
list_a[4] = 7  # [{1,1,1,1},{1,2,1},{2,1,1},{3,1},  {1,1,2}{2,2},  {1,3} ]
#上面是一个介绍过程,使用循环我们可以写成:
list_b = [0] * (n + 1)
list_b[0] = 1  # 初始化,方便计算,正常应该是0的
for i in range(1, n + 1):  # 循环给每一总台阶为i时计算走法
    for s in steps:  # 计算总台阶为i时最后一步为 s的走法
        if i >= s:  # 必须总台阶数大于步数才有走法
            list_b[i] += list_b[i - s]  # 总台阶数为 i时计算各个最后一步为s的走法的和
print(list_b[1])
print(list_b[2])
print(list_b[3])
print(list_b[4])
print(list_b[5])
print(list_b[6])
print(list_b[15])
C:\Python39\python.exe D:/PyWork/test_script/leetcode20220212.py
1
2
4
7
13
24
5768

凑金额
硬币面值:1,2,5 —这种场景与爬楼梯不一样,你在挑选时可以1,2,1或2,1,1的凑出4块钱,但是最后还是只算一种组合方式。
此时需要用背包算法来进行考虑
总金额:15
求组合方式

'''
背包算法就是指一堆指定面额/体积/重量的物品放入背包,求最大数量/方法/价值等内容的算法
如果物品每样只有一个就是0,1背包,方或者不放;不限制数量就是完全背包
这个场景就是不限制物品数量的场景
无序的话我们就不考虑取硬币的顺序而是考虑取硬币的数量。
我们可以用dp表(dynamic programming动态规划)表来进行推算

总金额 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |   # 下面的行往下一次表示只有1面额,有1和2面额,有1,25三种面额的情况
    1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |   #这行表示在只有1这一种面值的情况下,此时只会有一种组合方式
面额 2 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | 5 |       #这行表示只有1和2两种面额时的场景
    5 | 1 | 1 | 2 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |         #这行表示有1,2,5三种面额的场景
金额为0时,没有组合方式    
当我们只选择面值为1(i=0)的硬币时,不管总金额j>0为多少,只有一种组合方式,就是全部取面额为1的硬币,得到 dp[0][j]=1
当我们选择取面值为1/2两种硬币时,有不取2面额的取法,有取2面额的取法。当总金额每增加2,面额2就可以增加1,取法数也增加1
跟上面爬楼梯的思考方式类似,在同金额面值不同总金额的条件下发现也有F[i][j] = F[i][j-coins[i])+1的算法,
在不同面值同金额时就是F[i][j] = F[i-1][j]+j//coins[i]
我们可以先循环总金额(意思是以面额为外层,里面为金额,先循环完里面再循环一次外面),再循环面值
'''
coins = [1, 2, 5]
n = 15
dp = [[0] * (n + 1) for i in range(len(coins))]  # 初始化dp表
for i in range(n + 1):
    dp[0][i] = 1  # 第一行只有一个面额硬币,组合始终是1
for j in range(len(coins)):
    dp[j][0] = 1  # 初始第1化列,为了方便迭代作为1
[print(i) for i in dp]  # 可以看看初始化的dp表
for i in range(1, len(coins)):  # 遍历币种,第一种全是1就跳过了
    for j in range(1, n + 1):  # 先循环金额
        if j >= coins[i]:  # 考虑1块钱时没有往前退两块到-1的场景。这个条件主要在i小于2和小于5时对对应的2/5面值生效
            dp[i][j] = dp[i][j - coins[i]] + dp[i - 1][j]
            # dp[i - 1][j]表示不取i币种时的取法,dp[i][j - coins[i]]表示取i币种时(你可以看最后的打印dp[2][8]和dp[2][3],8金额的取法就是在3金额的取法后面各加了一个5面值,取法数不变的)
        else:  # 金额要大于币种才计算,1块钱时使用2块的面额用不了,此时只有1块的取法
            dp[i][j] = dp[i - 1][j]
print("========================完成计算后的dp表============")
[print(i) for i in dp]
print(dp[2][1])  # 1 [{1}]
print(dp[2][2])  # 2 [{1,1},{2}]
print(dp[2][3])  # 2 [{1,1,1},{1,2}]
print(dp[2][4])  # 3 [{1,1,1,1},{1,1,2},{2,2}]
print(dp[2][5])  # 4 [{1,1,1,1,1},{1,1,1,2},{1,2,2},{5}]
print(dp[2][6])  # 5 [{1,1,1,1,1,1},{1,1,1,1,2},{1,1,2,2},{2,2,2},{1,5}]
print(dp[2][7])  # 6 [{1,1,1,1,1,1,1},{1,1,1,1,1,2},{1,1,1,2,2},{1,2,2,2},{1,1,5},{2,5}]
print(dp[2][8])  # 7 [{1,1,1,1,1,1,1,1},{1,1,1,1,1,1,2},{1,1,1,1,2,2},{1,1,2,2,2},{2,2,2,2},{1,1,1,5},{1,2,5}]
print(dp[2][15])
C:\Python39\python.exe D:/PyWork/test_script/leetcode20220212.py
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
========================完成计算后的dp表============
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8]
[1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 14, 16, 18]
1
2
2
3
4
5
6
7
18
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值