零钱找零问题 总结 (最少的硬币数 ,所有组合数)

// 零钱找零问题.cpp : 定义控制台应用程序的入口点。
//
/*
动态规划思路:
dp[i][j]表示找零钱为i,从第1个硬币到第j个硬币选出最小的数量。money 为要找的钱数;coins数组存放每个硬币的面值,数量无限。
递推公式为:dp[i][j] = min{ dp[i-k*coins[j]][j-1]+k ;} 和,dp[i][j] 的最小者。 k为第j次选择面值为coins[j]的数量。确保k*coins[j]<=money。
初始值dp[i][0]=-INF,第0次选择设为不可达,之后依然可能有不可达的状态,dp[0][0]=0;当金钱为0时,不用找零,所以最少数量为0;这是所有解空间的最初起点。
*/
#include "stdafx.h"
#include <iostream>
using namespace std;
#define N 4
#define M 15
#define INF 1000
int dp[M+1][N+1];
int _tmain(int argc, _TCHAR* argv[])
{
	int coins[N+1]={0,1,3,9,10};
	//int money;// to be changed
	//cout<<"input the money to be changed "<<endl;
	//cin>>money;
	//money=15;
	for(int i=0;i<=M;i++)
	{
		dp[i][0]=INF;//这里和饮料供货不一样,一个找最大值,这里找最小值,所以设为无穷大
	}
	//for(int i=0;i<=N;i++)
	//{
	//	dp[0][N]=0;
	//}
	dp[0][0]=0;
	for (int j=1;j<=N;j++)
	{
		for (int i=0;i<=M;i++)
		{
			dp[i][j]=INF;
			for (int k=0;k*coins[j]<=i;k++)
			{
				//dp[i][j]=dp[i][j-1];
				if (dp[i-k*coins[j]][j-1]!=INF&&dp[i-k*coins[j]][j-1]+k<dp[i][j])
				{
					dp[i][j] = dp[i-k*coins[j]][j-1]+k;
				}
			}
		}
	}
	for (int i=0;i<=M;i++)
	{
		for (int j=0;j<=N;j++)
		{
			cout<<dp[i][j]<<" ";
		}
		cout<<endl;
	}
	system("pause");
	return 0;
}

上面解决 最少的硬币数,同编程之美里的饮料供货问题极度相似

下面的方法解决所有的组合数,分两种情况,当含有单元1的情况,此时不管多钱肯定能破开;和 不含有单元1的情况,此时对于任意所给的钱,不一定能破开,这种方法有两种实现,一种是i从小到大,一种是i从大到小,愚笨的分析呀,只是还没愚笨到底。

#define NUM 7
//int money[NUM] = {1, 2, 5, 10, 20, 50, 100};  
int money[NUM] = {1, 3, 4, 10, 20, 50, 100};
// 动态规划解法(完全背包)   
int NumOfCoins(int value)  
{  
	int dp[7][1010];  
	for(int i = 0; i <= value; ++i)
		dp[0][i] = 1;  //why 1 ? 所有的钱数为i都有一种就是用全1来表示。若果给出的钱不能用money中的钱表示,或者说money中没有为1的零钱呢?
// dp[0][j] = 1 为1是全用1来表示;for循环里会让dp[i][0] = 1 表示money[i] 本身就是一种方案
	// 正是因为有1,所以情况有所不同,因为有1,所以任何钱肯定能有全1来表示,所以dp不会有不可达状态
	for(int i = 1; i < NUM; ++i)
	{
		for(int j = 0; j <= value; ++j)  
		{
			if(j >= money[i])  //money[i] start from 2 
				dp[i][j] = dp[i][j-money[i]] + dp[i-1][j];  
			else  
				dp[i][j] = dp[i-1][j];  
		}  
	}  
	for(int i=0;i<NUM;i++)
	{
		for (int j=0;j<=value;j++)
		{
			cout<<dp[i][j]<<" ";
		}
		cout<<endl;
	}
	return dp[6][value];
}
#define NUM2 6
 //int money2[NUM2] = {0 ,4, 5, 10, 20, 50};  // 所给的钱可能破不开
  int money2[NUM2] = {4, 5, 10, 20, 50};  // 真是愚笨呀,完全可以money2从0 开始,只需下面的部分用 money[i-1] 即可。
//int money2[NUM2] = {0 ,50, 20, 10, 5, 4};  // i 可以从大到小

int NumOfCoins2(int value)  
{  
	int dp[7][1010];  
	for(int j = 0; j <= value; ++j)
	{
		dp[0][j] = 0;
	}
	for (int i = 0;i<NUM2;i++)
	{
		dp[i][0] = 1;//money[i]本身。
	}
	//dp[0][0] = 0;
	for(int i = 1; i < NUM2; ++i) // start from 2 
	{
		for(int j = 1; j <= value; ++j)
		{
			/*dp[i][j]=-INF;
			dp[i][j] = 0;
			for (int k = 0; dp[i-1][j]!=-INF;k++)
			{
				if(k*money2[i] <= j && dp[i][j-money2[i]]!=-INF)  //money[i] start from 2 
					dp[i][j] = dp[i][j-money2[i]] + dp[i-1][j];  
				else  
				{
					dp[i][j] = dp[i-1][j];  
					break;
				}
			}*/
			dp[i][j] = 0;//可能破不开,所以初始为0,0代表破不开,为什么不初始化为-INF?
			//如果初始化为-INF那么下面赋值的时候,要判断 dp[i][j-money2[i]]和 dp[i-1][j]是否为-INF,但是dp[i-1][j];参与了计算,即使是不可达状态,也会参与计算当前的方案数;
			//比如dp[i-1][j]是不可达,而dp[i][j-money2[i]]是可达的状态,此时要分别判断。像下面那样
			//for (int k = 0; ;k++)
			//{
				//为什么不用k了呢?因为根本就不用,不同于部分背包问题
				if(money2[i-1] <= j)  //对于money从0开始,而dp从1开始(dp【0】【j】作为了边界了)这部要用 money2[i-1]
					dp[i][j] = dp[i][j-money2[i-1]] + dp[i-1][j];  //dp[i][j] = dp[i][j-money2[i-1]] + dp[i-1][j];
				else  
				{
					dp[i][j] = dp[i-1][j];  
					//break;
				}
				/*如果用-INF表示不可达状态,分这么多情况考虑
				if (money2[i] <= j && dp[i][j-money2[i]] != 0 && dp[i-1][j] != 0)
				{
					dp[i][j] = dp[i][j-money2[i]] + dp[i-1][j];
				}
				if (money2[i] <= j && dp[i][j-money2[i]] != 0 && dp[i-1][j] == 0)
				{
					dp[i][j] = dp[i][j-money2[i]];
				}
				if (money2[i] <= j && dp[i][j-money2[i]] == 0)
				{
					dp[i][j] = dp[i-1][j];
				}
				if (money2[i] > j && dp[i-1][j] != 0)
				{
					dp[i][j] = dp[i-1][j];
				}*/
			//}
		}  
	}  
	for(int i=0;i<NUM2;i++)
	{
		for (int j=0;j<=value;j++)
		{
			cout<<dp[i][j]<<" ";
		}
		cout<<endl;
	}
	return dp[NUM2-1][value];
}
#define NUM3 5
int money3[NUM3] = {4, 5, 10, 20, 50};  // 只是起始边界不一样
int NumOfCoins3(int value)  
{  
	int dp[NUM3+1][1010];  
	for(int j = 0; j <= value; ++j)
	{
		dp[NUM3][j] = 0; // i from NUM3 to 0
	}
	for (int i = 0;i<=NUM3;i++)
	{
		dp[i][0] = 1;//money[i]本身。j from 0 to value
	}
	for(int i = NUM3-1; i >= 0; --i) // start from 2 
	{
		for(int j = 1; j <= value; ++j)
		{
			dp[i][j] = 0;
			if(money3[i] <= j)  //money[i] start from 2 
				dp[i][j] = dp[i][j-money3[i]] + dp[i+1][j];  
			else  
			{
				dp[i][j] = dp[i+1][j];  
			}
		}  
	}  
	for(int i=0;i<=NUM3;i++)
	{
		for (int j=0;j<=value;j++)
		{
			cout<<dp[i][j]<<" ";
		}
		cout<<endl;
	}
	return dp[0][value];
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值