python-动态规划题目-凑硬币

题目

你有三种硬币,面值分别为2元,5元,7元,每种硬币都足够多,买一本书需要27元。问:如何用最少的硬币组合正好付清,不需要对方找钱。

递归方法

递归方法的思路是判断最后一步,如果最后一步的数不能被2、5、7整除的话,那么返回无穷大,只有钱刚好凑齐的情况下,返回0

a,b,c = map(int,input('输入a,b空格隔开:').split())
def coin(X):
    res = float('inf')   ##初始情况,将其赋予无穷大
    if(X == 0):
        return 0   ##钱刚好凑齐
    if(X>=2):
        res = min(coin(X-2)+1,res)   
    if(X>=5):
        res = min(coin(X-5)+1,res)
    if(X>=7):
        res = min(coin(X-7)+1,res)
    return res
print(coin(27))  

但是用递归的话浪费的空间太多了,而且时间复杂度是2的N此方,如下图所示:
在这里插入图片描述

动态规划

所以我们可以选择用动态规划来做。

首先确定状态

最后一步

最优策略必定是K枚硬币a1, a2,…, aK 面值加起来是27。

找出不影响最优策略的最后一个独立角色,这道问题中,那枚最后的硬币“aK”就是最后一步。把aK提取出来,硬币aK之前的所有硬币面值加总是27- aK
因为总体求最硬币数量最小策略,所以拼出27- aK 的硬币数也一定最少(重要设定)。
在这里插入图片描述

分解成子问题

最后一步aK提出来之后,我们只要求出“最少用多少枚硬币可以拼出27- aK”就可以了。

这种与原问题内核一致,但是规模变小的问题,叫做子问题。

为简化定义,我们设状态f(X)=最少用多少枚硬币拼出总面值X。

我们目前还不知道最后的硬币aK面额多少,但它的面额一定只可能是2/5/7之一。

如果aK是2,f(27)应该是f(27-2) + 1 (加上最后这一枚面值2的硬币)
如果aK是5,f(27)应该是f(27-5) + 1 (加上最后这一枚面值5的硬币)
如果aK是7,f(27)应该是f(27-7) + 1 (加上最后这一枚面值7的硬币)

除此以外,没有其他的可能了。
至此,通过找到原问题最后一步,并将其转化为子问题。

为求面值总额27的最小的硬币组合数的状态就形成了,用以下函数表示:
f(27) = min{f(27-2)+1, f(27-5)+1, f(27-7)+1}

在这里插入图片描述

其次确定转移方程

为求面值总额27的最小的硬币组合数的状态就形成了,用以下函数表示:
f(27) = min{f(27-2)+1, f(27-5)+1, f(27-7)+1}

最后确定初始条件和边界情况

故对边界情况设定如下:
如果硬币面值不能组合出Y,就定义f[Y]=正无穷

例如f[-1]=f[-2]=…=正无穷;
f[1] =min{f[-1]+1, f[-4]+1,f[-6]+1}=正无穷,

特殊情况:本题的F[0]对应的情况为F[-2]、F[-5]、F[-7],按照上文的边界情况设定结果是正无穷。
但是实际上F[0]的结果是存在的(即使用0个硬币的情况下),F[0]=0。

可是按照我们刚刚的设定,F[0]=F[0-2]+1= F[-2]+1=正无穷。
岂不是矛盾?

这种用转移方程无法计算,但是又实际存在的情况,就必须通过手动定义。
这里手动强制定义初始条件为:F[0]=0.

而从0之后的数值是没矛盾的,比如F[1]= F[1-2]+1= F[-1]+1=正无穷(正无穷加任何数结果还是正无穷);F[2]= F[2-2]+1= F[0]+1=1……

实际面试中求解动态规划类问题,正确列出转移方程正确基本上就解决一半了。

##有三枚不同的硬币2、5、7凑成27,求最少的硬币组合
nums = [2,5,7]
S = 27
first_list = [float('inf') for k in range(S+1)]  ##赋予初值
first_list[0] = 0  ##初始条件
for i in range(1,S+1):   #由小到大F[0]、F[1]、F[2]...
    for j in range(len(nums)): #F[0]-2、F[0]-5、F[0]-7 ....
        if((i-nums[j]>=0) and (first_list[i-nums[j]]!=float('inf'))):  ##如果(剩下的钱够凑&&F[i]-j是不是无穷大)
            first_list[i] = min(first_list[i-nums[j]]+1,first_list[i])  ##比较次数
if first_list[S] == float('inf'):  #类似爬楼梯算法,最后计算的是S的值(如图所示)
    print("没有这种硬币组合")
else:
    print(first_list[S]) #因为每一步都已经是最小的组合了,到最后一步只需要直接输出结果

在这里插入图片描述

两种方法的区别

递归不会保存每一初始值,所以重复的计算量很多。时间复杂度=2的N此方
而动态规划类似于爬山算法,是从小到大将每一个数保存在列表中从而进行计算,不需要进行重复计算,时间复杂度=硬币种类*凑成的总数字。

参考文章

java实现的凑硬币版本

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值