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

本文探讨了使用2元、5元、7元硬币组合成27元的最少硬币数量问题,通过递归和动态规划两种方法进行解析,详细阐述了动态规划的步骤,包括确定状态、转移方程、初始条件和边界情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

你有三种硬币,面值分别为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实现的凑硬币版本

<think>嗯,用户之前问过关于C语言中同名静态变量和局部变量的问题,现在又问零钱问题的动态规划解法。这说明用户可能在学习编程或算法,尤其是动态规划部分。我需要先确定用户的基础,可能对动态规划有一定了解,但需要更详细的解释。 零钱问题是一个经典的动态规划问题,通常题目是给定不同面额的硬币和一个总金额,计算成总金额所需的最少硬币数。如果无法出,则返回-1。这个问题适合用动态规划解决,因为可以分解为子问题,并且存在重叠子问题。 首先,我需要解释动态规划的基本思想,然后应用到零钱问题上。可能需要先描述问题,再分步骤说明如何构建动态规划表,如何初始化,如何递推,以及时间复杂度的分析。还要注意边界条件,比如总金额为0的情况,或者硬币面额无法组合的情况。 用户可能希望看到具体的例子,比如硬币面额是[1,2,5],总金额是11,最少硬币数是3(5+5+1)。这时候用动态规划的话,dp数组的每个元素dp[i]表示成金额i所需的最少硬币数。初始化dp[0]=0,其他初始化为一个较大的值,比如无穷大。然后遍历每个金额,对于每个硬币,如果硬币面额小于等于当前金额,就更新dp[i]为min(dp[i], dp[i-coin]+1)。 可能用户还会关心为什么贪心算法不适用,比如如果硬币面额不是按倍数设计的,比如[1,3,4],总金额为6,贪心会选择4+1+1,但最优解是3+3。这时候需要动态规划才能得到正确结果。 此外,需要提到状态转移方程的正确性,以及如何避免重复计算。可能还要比较自顶向下和自底向上的方法,但通常零钱问题用自底向上更直观。 最后,总结一下动态规划解法的步骤,并给出代码示例,比如用Python或Java,但用户之前问的是C语言,不过这里可能需要用伪代码或者中文描述,避免具体语法问题。同时注意用户可能希望了解时间复杂度和空间复杂度,以及优化方法,比如空间压缩,不过零钱问题一般不需要,因为维度较低。 需要确保回答结构清晰,分步骤讲解,帮助用户逐步理解问题。可能用户需要实际应用的例子,所以举几个例子会有帮助。同时提醒用户注意边界条件和可能的错误,比如硬币面额有0的情况,或者总金额为负数的情况,但在问题中通常假设输入是有效的。</think>好的,我将用**动态规划(DP)**的核心思想,结合**实际案例**,逐步拆解零钱问题的解决思路。以下是最清晰的讲解: --- ### 一、问题定义 **题目**:给定不同面额的硬币数组 `coins`(例如 `[1, 2, 5]`)和一个目标金额 `amount`(例如 `11`),计算出该金额所需的**最少硬币数量**。若无法出,返回 `-1`。 **示例**: - 输入:`coins = [1,2,5], amount = 11` - 输出:`3`(`5+5+1`) --- ### 二、动态规划核心思想 #### 1. **定义状态** - `dp[i]`:表示出金额 `i` 所需的**最少硬币数** - **最终目标**:求 `dp[amount]` #### 2. **状态转移方程** 对于每个金额 `i`,遍历所有硬币 `coin`: - 若 `coin ≤ i`,则 `dp[i] = min(dp[i], dp[i - coin] + 1)` - **含义**:如果使用这枚硬币,总硬币数等于出 `i-coin` 的最少硬币数加1 #### 3. **初始化** - `dp[0] = 0`:出金额0需要0个硬币 - 其他 `dp[i]` 初始化为 `∞`(或 `amount + 1`,因为最多硬币数为 `amount`) --- ### 三、具体步骤拆解(以 `coins = [1,2,5], amount = 11` 为例) #### 1. 初始化DP表 | 金额i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |-------|---|---|---|---|---|---|---|---|---|---|----|----| | dp[i] | 0 | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | ∞ | #### 2. 递推过程 - **i=1**:可用的硬币为 `1` `dp[1] = min(dp[1], dp[1-1] + 1) = 0 + 1 = 1` - **i=2**:可用硬币为 `1,2` - 用 `1`:`dp[2-1] + 1 = 1 + 1 = 2` - 用 `2`:`dp[2-2] + 1 = 0 + 1 = 1` → `dp[2] = 1` - **i=3**:可用硬币为 `1,2` - 用 `1`:`dp[3-1] + 1 = 1 + 1 = 2` - 用 `2`:`dp[3-2] + 1 = 1 + 1 = 2` → `dp[3] = 2` - ... 以此类推,直到 `i=11` #### 3. 最终DP表(部分关键值) | 金额i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |-------|---|---|---|---|---|---|---|---|---|---|----|----| | dp[i] | 0 | 1 | 1 | 2 | 2 | 1 | 2 | 2 | 3 | 3 | 2 | 3 | --- ### 四、代码实现(Python示例) ```python def coinChange(coins, amount): dp = [float('inf')] * (amount + 1) dp[0] = 0 for i in range(1, amount + 1): for coin in coins: if coin <= i: dp[i] = min(dp[i], dp[i - coin] + 1) return dp[amount] if dp[amount] != float('inf') else -1 print(coinChange([1,2,5], 11)) # 输出3 ``` --- ### 五、复杂度分析 - **时间复杂度**:`O(amount × len(coins))` - **空间复杂度**:`O(amount)` --- ### 六、关键点总结 1. **为什么贪心算法不适用** 例如 `coins = [1,3,4], amount = 6`,贪心会选 `4+1+1`(共3枚),但最优解是 `3+3`(2枚)。 2. **DP的适用性** - 分解为重叠子问题(每个金额依赖更小金额的解) - 无后效性(当前选择不影响后续状态) 3. **边界条件处理** - `amount = 0` → 返回0 - `coins` 包含无法组成 `amount` 的情况(如 `coins = [2], amount = 3` → 返回-1) --- 通过以上步骤,可以清晰理解动态规划零钱问题中的应用逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值