欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

动态规划问题(2) 之 由凑零钱认识最优子结构

程序员文章站 2022-07-03 11:47:28
...

由凑零钱问题认识 最优子结构

问题描述:

有k种⾯值的硬币,⾯值分别为 c1, c2...ck,每种硬 币的数量⽆限,再给⼀个总⾦额 amount,问你最少需要⼏枚硬币凑出这个⾦额,如果不可能凑出,算法返回 -1。

如:k = 3,⾯值分别为1,2,5,总⾦额amount = 11 。那么最少需要3枚硬币凑出,即11= 5 + 5 + 1。

1、暴力递归

这是一个动态规划的问题,其子问题必须互相独立,即选择每个金额的硬币都是相互独立的,

⽐如你想求  amount  = 11时的最少硬币数(原问题),如果你知道凑出 amount =10的最少硬币数(⼦问题),你只需要把⼦问题的答案加⼀(再选⼀枚⾯值为1的硬币) 就是原问题的答案,因为硬币的数量是没有限制的,⼦问题之间没有相互制约,是互相独⽴的。这个过程即符合最优子结构,每一个子问题都求得最少,最终的原问题就会是最少。

对于一个动态规划问题,就要思考如何列出正确的状态转移⽅程?

先确定状态,也就是原问题和⼦问题中变化的变量。由于硬币数量⽆限,所以唯⼀的状态就是⽬标⾦额amount。

然后确定 dp  函数的定义:当前的⽬标⾦额是 n ,⾄少需要dp(n) 个硬 币凑出该⾦额。

然后确定选择并择优,也就是对于每个状态,可以做出什么选择改变当前状态。具体到这个问题,⽆论当的⽬标⾦额是多少,选择就是从⾯额列表 coins 中选择⼀个硬币,然后⽬标⾦额就会减少。

最后再明确 base case,显然⽬标⾦额为0 时,所需硬币数量为 0;当⽬标⾦额⼩于0 时,⽆解,返回-1。

动态规划问题(2) 之 由凑零钱认识最优子结构

 此时,问题已经解决,接下来就是需要消除重叠子问题,给出递归树如下

动态规划问题(2) 之 由凑零钱认识最优子结构

 2、带备忘录的递归

动态规划问题(2) 之 由凑零钱认识最优子结构

很显然备忘录⼤⼤减⼩了⼦问题数⽬,完全消除了⼦问题的冗余,所以⼦问题总数不会超过⾦额数n,即⼦问题数⽬为 O(n)。处理⼀个 ⼦问题的时间不变,仍是O(k),所以总的时间复杂度是 O(kn)。

3、dp数组的迭代解法

使用自底向上的 dp table 来消除重叠子问题:

    dp[i] = x 表⽰,当⽬标⾦额为 i  时,⾄少需要 x 枚硬币。

int coinChange(vector<int>& coins, int amount)
{
    //	数组⼤⼩为amount+1,初始值也为amount+1
    vector<int>	dp(amount + 1,amount + 1);
    //	base case
    dp[0] = 0;
    for	(inti=0; i<dp.size(); i++)
    {
        //内层for在求所有⼦问题+1的最⼩值
        for(int coin : coins)
        {
            //	⼦问题⽆解,跳过
            if(i - coin < 0) continue;
            dp[i]= min(dp[i], 1+dp[i-coin]);
        }
    }
    return (dp[amount] == amount + 1) ? -1 : dp[amount];
}

 

相关标签: 动态规划