给定数组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;
}