DP最少硬币问题:用最少数量的硬币凑够一定价值

这次同样是动态规划的问题,其核心思想与此前的viterbi算法一样:基于局部的子最优寻找全局最优,每走一步都是取决于前一步的最优解。因而,动态规划方法的计算复杂度也会大大降低。

这次的硬币问题是一个经典的动态规划问题模型:现在拥有面值为3元、6元、7元的硬币若干;问:如何用最少数量的硬币凑出18元?

分析:在考虑动态规划之前,可能我们会使用贪心算法来实现,但显然使用贪心算法7+7+3=17,并不能保证找出最优解。(贪心算法每次会选取可能的最大面值来实现硬币总数最少)并且在该问题中,最优解显然存在:6+6+6=18(最少3个硬币)。

关键点:找到状态转换方程:”d(i)=d(j)+1(j为i的前一个阶段)”,并理解为何是“+1”?

现在,我们开始考虑动态规划(DP)的方法。我们设d(i)为凑够i元所需的最少硬币数目,显然d(0)=0,0元不用凑;d(1)=99999,硬币的最小面值为3元,1元显然凑不了(与之前dijkstra算法一样用99999来表示不能实现的情况);同理,d(2)=99999;当要凑够3元的时候,只需要在凑够0元的基础上再凑一个3元硬币就OK了,所以d(3)=d(3-3)+1。然后接下来:d(4)=99999;d(5)=99999;到凑6元的时候我们就发现了问题:可以用2个3元或者1个6元凑够6元。因此我们需要比较min{d(6-3)+1,d(6)=d(6-6)+1};注意,这里”d(6-3)+1″的意义在于:在凑够3元的情况下再凑3元。(即之前所说的:基于此前的状态再走一步)。但是,这里”d(6-3)+1=2>d(6-6)+1=1″,最优解应是:d(6)=d(6-6)+1=1;接下来,d(7)=d(7-7)+1=1;好了,到这里,这个阶段就结束了,我们分别对d(1)~d(7)分别找到了最优解(包括无解的情况)。

好,接下去一个阶段。d(8)=99999;到凑9元了!要凑够9元,我们可以从前一个步骤的最优解解集中选一个,然后再走一步!d(9)=min{d(9-3)+1,d(9-6)+1}=2。显然,在计算d(6)时,我们就不需要再计算要选择1个6元还是2个3元了!因为前个步骤已经计算并保存了凑够6元的最优解d(6)!这就是动态规划算法的核心所在,并能降低算法复杂度的原因!

好了,不说废话了,直接上代码(c++)!

方法一:

/*递归做法*/

#include<iostream>
using namespace std;

int coins[3] = {3,6,7}; 
int d[19] ;  //存放0到18元组成的硬币数

int min(int a,int b)
{
    return (a<=b)? a:b;
}

void min_coins(int i,int num) //从i元开始凑够num元
{
    if(i == 0)
    {
        d[i] = 0;
        min_coins(1,num);
        return;
    }
    else
    {
        int MIN = 9999;
        for(int j=0;j<3;j++)
        {
            if(i>=coins[j])
            {
                MIN = min(d[i-coins[j]]+1,MIN);
            }
        }
        d[i] = MIN;

        if(i == num)return;
        else
        min_coins(i+1,num);
    }
}
int main()
{
    min_coins(0,18);        //表示要凑齐18元的硬币
    for(int i=0;i<19;i++)
    {
        cout<<"凑齐"<<i<<"元,至少需要"<<d[i]<<"枚硬币"<<endl;
    } 
    return 0; 
}

方法二:

/*循环做法*/

#include<iostream>
using namespace std;

int coins[3] = {3,6,7}; //硬币面值
int d[19]; //存放0到18元组成的硬币数

//比较大小
int min(int a, int b){
    return (a<=b) ? a:b;
}; 

//初始化
void initD(){
    for(int i=0; i<19; i++){
        d[i] = 99999;
    }
}; 

//主要功能实现
void min_coins(){
    for(int i=0; i<19; i++){
    if(i==0){
        d[0]=0;
        continue;
    }
    int MIN = 99999; //重设MIN
        for(int j=0; j<3; j++){
            if(i>=coins[j]){
                MIN = min(d[i-coins[j]]+1,MIN); //比较大小,寻找最佳
            }
            else break; //第一个不合条件就退出,优化算法
            d[i] = MIN;
        }
    }
}

void main(){
    initD();
    min_coins();
    for(int k=0; k<19; k++){
        cout<<"凑齐"<<k<<"元,至少需要"<<d[k]<<"枚硬币"<<endl; //输出结果
    }
}

    原文作者:俊爷拒做学渣
    原文地址: https://www.jianshu.com/p/0c2041a55735
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞