换钱的方法数

来源:互联网 发布:如何编译linux源码 编辑:程序博客网 时间:2024/05/08 16:19

给定数组arr,arr中所有的值都为整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,在给定一个整数aim代表要找的钱数,求换钱的方法有多少种。


方法一:利用递归,时间复杂度O(aim^N)

arr=[10,b,c...], aim=100,分析过程如下:

1.用0张10元的货币,让[b,c......]组成剩下的100,最终方法数记为res1.

2.用1张10元的货币,让[b,c......]组成剩下的90,最终方法数记为res2.

3.用2张10元的货币,让[b,c......]组成剩下的80,最终方法数记为res3.

::::::

10.用10张10元的货币,让[b,c......]组成剩下的0,最终方法数记为res11.

那么res1+res2+res3+......+res11的值就是总的方法数。

int money1(int* arr, int len, int index,int aim){    if(len==0 || aim<0)        return 0;    int res =0;    if(index==len)        res = (aim==0?1:0);    else        {            for(int i=0; i*arr[index]<=aim; i++)            {                res += money1(arr,len,index+1,aim-arr[index]*i);            }        }    return res;}

方法二:动态规划方法,时间复杂度O(N*aim)

生成行数为N、列数为aim+1的矩阵dp,dp[i][j]的含义是在使用arr[0......i]货币的情况下,组成钱数j有多少种方法。dp[i][j]值得求法如下:

1.对于矩阵dp第一列的值dp[..][0],表示组成钱数为0的方法数,很明显是1种,也就是不使用任何货币。所以dp第一列的值统一设置为1.

2. 对于矩阵dp第一行的值dp[0][..],表示只能使用arr[0]这一种货币的情况下,组成钱的方法数,如,arr[0]=10时,能组成的钱数只有10,20,30.......所以,令dp[0][i*arr[0]]=1.

3. 出第一行和第一列外的其他位置,记为位置(i,j).。dp[i][j]的值是以下几个值的累加。

完全不用arr[i]货币,只使用arr[0...i-1]货币时,方法数为dp[i-1][j];

用1张arr[i]货币,剩下的钱用arr[0...i-1]货币组成,方法数为dp[i-1][j-arr[i]];

2张arr[i]货币,剩下的钱用arr[0...i-1]货币组成,方法数为dp[i-1][j-2*arr[i]];

::::::::

k张arr[i]货币,剩下的钱用arr[0...i-1]货币组成,方法数为dp[i-1][j-k*arr[i]]; (j-k*arr[i]>=0)

步骤3中,第一种情况方法数为dp[i-1][j],而第二种情况一直到第k种情况的方法数累加值其实就是dp[i][j-arr[i]]的值。所以步骤3可以简化为dp[i][j]=dp[i][j-arr[i]]+dp[i-1][j]

int money2(int* arr, int len, int aim){    if(len==0 ||aim<0)        return 0;    int **dp;    dp = new int*[len];    for(int i=0; i<len; i++)    {        dp[i]=new int[aim+1];    }    for(int i=0; i<len; i++)    {        dp[i][0]=1;    }    for(int j=1; j<=aim; j++)    {        dp[0][j]=0;    }    for(int j=1; j*arr[0]<=aim; j++)    {        dp[0][j*arr[0]]=1;    }    for(int i=1; i<len; i++)    {        for(int j=1; j<=aim; j++)        {            if(j>=arr[i] )            {                dp[i][j]=dp[i][j-arr[i]]+dp[i-1][j];            }            else                dp[i][j] = dp[i-1][j];        }    }    int res = dp[len-1][aim];    for(int i=1; i<len; i++)    {        delete []dp[aim+1];    }    delete []dp;    return res;}

方法三:时间复杂度为O(N*aim)的动态规划方法结合空间压缩技巧。额外的空间复杂度降至O(aim)

int money3(int* arr, int len, int aim){    int *dp;    dp = new int[aim+1];    dp[0]=1;    for(int i=1; i<=aim; i++)    {        dp[i]=0;    }    for(int j=1; j*arr[0]<=aim; j++)    {        dp[j*arr[0]]=1;    }    for(int i=1; i<len; i++)    {        for(int j=1; j<=aim; j++)        {            if(j-arr[i]>=0)            {                dp[j] = dp[j-arr[i]]+dp[j];            }            else                dp[j]=dp[j];        }    }    int res=dp[aim];    return res;}



三种方法的测试:


#include <iostream>#include<stdlib.h>int money1(int* arr, int len, int index,int aim);int money2(int* arr, int len, int aim);int money3(int* arr, int len, int aim);using namespace std;#define maxnum INT_MAX;int main(){    int arr[] = {5,10,25,1};    int aim = 15;    size_t arr_len=sizeof(arr)/sizeof(int);    int means1 = money1(arr,arr_len,0,aim);    int means2 =money2(arr,arr_len,aim);    int means3 =money3(arr,arr_len,aim);    cout<<"means: "<<means1<<endl;    cout<<"means: "<<means2<<endl;    cout<<"means: "<<means3<<endl;    return 0;}