作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。
会一些的技术:数据分析、算法、SQL、大数据相关、python
欢迎加入社区:码上找工作
作者专栏每日更新:
备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级
问题背景
硬币找零问题是一个经典的动态规划问题,在这个问题中,我们假设有无限数量的n种面额的硬币,要使用这些硬币组成特定金额(M),我们的目标是找到所需硬币数量的最小值。
问题描述
给定:
- 一个硬币面额的数组
coins
(例如 [1, 2, 5]) - 一个总金额
amount
(例如 11)
输出:
- 组成总金额所需的最小硬币数量(例如 11 = 5 + 5 + 1,输出 3)
- 如果无法组成该金额,则输出 -1
解题思路
动态规划的解决方案涉及构建一个数组来保存达到每个金额所需的最小硬币数量。我们从金额0开始逐步构建解决方案,直到金额M。
动态规划解法
定义状态
dp[i]
表示组成金额 i
所需的最小硬币数量。
状态转移方程
推导过程
我们知道,如果我们有一种面额为 c
的硬币,那么组成金额 i
可以由组成金额 i - c
再加上一枚面额为 c
的硬币得到。换句话说,如果我们知道了 dp[i - c]
,我们就可以通过加上一枚 c
面额的硬币来得到 dp[i]
。
这里有个关键的观察点:要得到 dp[i]
,我们需要考虑所有小于 i
的金额,并检查增加哪种面额的硬币会导致硬币总数最小。也就是说,我们要考虑所有可能的 c
,其中 c
是硬币的面额。
因此,对于每个 c
,dp[i]
可以这样计算:
dp[i - c] + 1
,这里的+1
表示加上一枚面额为c
的硬币。
由于我们想要的是最小硬币数量,所以我们会选择所有 dp[i - c] + 1
中的最小值。
初始化
我们初始化 dp[0] = 0
,因为金额0不需要任何硬币,其他金额初始化为一个较大的数,比如 amount + 1
或 float('inf')
。
代码示例
# 定义函数来计算组成特定金额所需的最小硬币数量 def coin_change(coins, amount): # 用一个大数初始化DP数组,表示初始状态无法达成 dp = [float('inf')] * (amount + 1) # 定义初始状态,0金额需要0硬币 dp[0] = 0 # 遍历所有金额,从1到目标金额 for i in range(1, amount + 1): # 遍历每个硬币 for coin in coins: # 如果当前硬币可以用(不会使金额变成负数) if i - coin >= 0: # 状态转移方程 dp[i] = min(dp[i], dp[i - coin] + 1) # 如果最终的金额状态仍然是初始化的大数,说明无解,返回-1 return dp[amount] if dp[amount] != float('inf') else -1 # 定义硬币面额和目标金额 coins = [1, 2, 5] amount = 11 # 调用函数并打印结果 result = coin_change(coins, amount) result
提供解决方案
如果 dp[amount]
是一个较大的数,则表示无解;否则,dp[amount]
就是所需的最小硬币数量。
算法分析
时间复杂度 O(nm):在硬币找零问题中,我们需要计算每个金额从1到目标金额 amount
的最小硬币数。对于每个金额,我们都需要遍历所有的硬币面额来确定最少的硬币数。
- 假设硬币的面额种类有
n
种。 - 假设目标金额为
m
。
对于每个金额 i
(i
从1到 m
),我们需要遍历所有硬币面额,所以时间复杂度为 O(nm)。
空间复杂度 O(m):在硬币找零问题中,我们通常使用一维数组来存储中间结果,这个数组的大小是目标金额 m
加1(为了从0开始计数)。因此,空间复杂度为 O(m)。
图解步骤
假设我们有面额为1, 2, 5的无限硬币,我们需要组成总金额11。
我们初始化 dp
为 [0, inf, inf, ..., inf]
(长度为12),然后开始填充 dp
数组:
dp[1]
最小是1(一个1元硬币)dp[2]
最小是1(一个2元硬币)dp[3]
最小是2(一个1元加一个2元)- ...
dp[11]
最小是3(两个5元加一个1元)
最后 dp[11]
是3,这是我们的答案。
1.初始状态(没有使用任何硬币):
2.使用1元硬币填充表格:
3.加入2元硬币后的变化(注意金额2,4,6,8,10的变化):
- 在只有1元硬币可用时,构成金额 i 需要 i 个硬币,因为1元硬币是唯一的选择。
- 加入2元硬币后,我们有机会减少构成某些金额所需的硬币数量。对于任何偶数金额,比如2、4、6等,我们可以用一个2元硬币替代两个1元硬币,这减少了硬币的总数。而对于比如金额3、5、7等含有奇数的金额,我们会尝试使用至少一个1元硬币,加上尽可能多的2元硬币。
4.加入5元硬币后的变化(注意金额5,10的显著减少):
总结
硬币找零问题是动态规划问题中的另一个经典案例。它展示了动态规划如何通过构建子问题的解决方案来解决原问题,并确保每一步都是优化的。这个方法既可以帮助我们找到所需的最小硬币数量,也可以用来说明如果没有可行的方案如何处理。
通过动态规划解决问题时,始终要记得定义清楚状态,找到状态转移方程,并正确地初始化状态。这样,我们就可以构建一个有效的算法来解决一系列复杂的优化问题。
欢迎关注微信公众号 数据分析螺丝钉