动态规划-找零钱问题
从贪心说起
我们知道贪心算法可以解决「硬币找零问题」,但是那只是在部分情况下可以解决而已。
那么有什么情况下不能用贪心算法吗?比如一个算法星球的央行发行了奇葩币,币值分别为{1、5、11},要凑够15元,这个时候贪心算法就失效了。
按照贪心算法的策略,我们先拿出最大面值的11,剩下的4个分别对应四个1元的奇葩币,这总共需要五个奇葩币才能凑够15元。
而实际上我们简单一算,就知道最少情况是拿出3个五元的奇葩币才能凑够15元
动态规划
我们要凑够这个 n,只要 n 不为0,那么总会有处在最后一个的硬币,这个硬币恰好凑成了 n
- 假设最后一个硬币为11的话,那么剩下4(15-11),这个时候问题又变成了,我们凑出 n-11 最少需要多少个币
- 如果假设最后一个硬币为 5 的话,这个时候问题又变成了,我们用现有的币值凑出 n-5 最少需要多少个币
我们的问题提可以不断被分解为「我们用现有的币值凑出 n 最少需要多少个币」,用 f(n) 函数代表 「凑出 n 最少需要多少个币」.
把「原有的大问题逐渐分解成类似的但是规模更小的子问题」这就是最优子结构,我们可以通过自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。
这个时候我们分别假设 1、5、11 三种面值的币分别为最后一个硬币的情况:
- 最后一枚硬币的面额为 11: min = f(4) + 1
- 最后一枚硬币的面额为 5: min = f(10) + 1
- 最后一枚硬币的面额为 1: min = f(14) + 1
假设凑的硬币总额为 n,那么 f(4) = f(n-11)、f(10) = f(n-5)、f(14) = f(n-1),我们得出以下公式:
\[f(n) = min \left\{f(n-1), f(n-5), f(n-11)\right\} + 1
\]
再具体到上面公式中 f(n-1) 凑够它的最小硬币数量是多少,是不是又变成下面这个公式:
\[f(n-1) = min\left\{f(n-1-1), f(n-1-5), f(n-1-11)\right\} + 1
\]
代码实现
# 面额:1,5,11 amount:15 (n)
def f(n):
res = float('INF')
if n ==0:
return 0
if n>=1:
res = min(f(n-1)+1,res)
if n>=5:
res = min(f(n-5)+1,res)
if n>=11:
res = min(f(n-11)+1,res)
return res