动态规划解决硬币找零问题(指定面额及各面额硬币数量)

参考博客:https://blog.csdn.net/qq_51666839/article/details/121865336

这里写出我的理解及借鉴的代码

假设面额种类有4种: int a[4] = {1,2,5,16};
对应的数目: int b[4] = {3,4,2,0};
要凑出的金额为:m
c[m+1] : 存放最少硬币数的数组, c[k]表示待找金额为k时所需最少硬币数,+1是因为c[0]=0

用动态规划的方法解决这个问题就是要先假设最坏情况是用无穷个硬币来凑出所需金额,然后再逐步迭代,在满足题目要求的前提下减少所需硬币个数,最后如果无法在满足题目要求的前提下更新所需个数(最后结果是无穷),说明用你现在的钱找不开。

  • 对a[4]遍历(int i):要凑出金额为m的钱,可以将这个问题拆解成用一个面额为a[i]的硬币并且凑出金额为m-a[i]的钱,转换成求解凑出m-a[i]的钱所需的最少硬币数。考虑所有面值所以用一个for循环对数组a[4]进行遍历。

  • 嵌套遍历b[i]次(int j):但由于你拥有的不同面额的硬币有限,不能让你随便挥霍。所以我们一张一张地挥霍。遍历a[4]时嵌套一个for循环遍历b[i],每个循环表示你挥霍了一个硬币a[i]。在这个循环中,我们更新存放最少硬币数的数组c[m+1]。

  • 嵌套对c[m+1]遍历(int k):上面我们说过,要先假设最坏情况是用无穷个硬币来凑出所需金额,再优化。所以先把c[m]初始化为无穷。然后从c[m]开始更新(从后往前(k=m->k=0)更新c[m+1], 这样当待凑金额k比面额a[i]小,必然不会用上a[i],就可以提前结束此轮对c[m+1]的更新了,并且由于c[k]的更新需要用到下标为k之前的结果,所以不能先更新前面的部分,一枚硬币不能凭空用两次),
    更新公式:c[k] = min(c[k], c[k - a[i]] + 1);
    即比较原本所需的最少数量c[k]、和用了面值为a[i]的硬币所需的最少数量c[k - a[i]] ,取较小的,由于要算上 面值为a[i]的硬币,所以是c[k - a[i]] + 1。

结束上述循环后即可得到结果c[m](若c[m]为无穷则输出-1)。

下面举例说明:
面额种类有4种: int a[4] = {1,2,4,9}; 对应的数目: int b[4] = {2,3,2,0};
假设待凑金额 m = 8
则c[9] 初始化为{ inf, inf, inf, inf, inf, inf, inf, inf}; 显然c[0] = 0;
对a[4]的遍历索引为i, 对b[i]的遍历索引为j, 对c[9]的遍历索引为k,

  • 假设先用面额为1的硬币(i=0)

面额为1的硬币有2个,我们先用1个*(j=0)*,然后从后往前更新存放最少硬币数的数组c[9].
当k=8,将问题拆解成: 已知用了1个面额为1的硬币,求凑出金额为k - a[i]=8-1=7所需最少硬币数(可以直接查数组c[k - a[i]]获得),由c[k] = min(c[k], c[k - a[i]] + 1); 此时c[8] = inf, ,c[7] + 1 = inf+1,所以更新后c[8]仍为inf
当k=7, k - a[i]=7-1=6, c[7] = min(c[7], c[6] + 1); c[6]=inf, 所以c[7]仍为inf…
当k=6,5,4,3,2,更新结果均为inf,直到
当k=1,k - a[i]=1-1=0, c[1] = min(c[1], c[0] + 1); c[0]=0, c[1]=inf, 所以更新后c[1]为1

此时c[9] ={ 0, 1, inf, inf, inf, inf, inf, inf, inf};

用了1个面额为1的硬币我们还可以继续用第2个,所以j=2,继续更新c[9].
当k=8, k - a[i]=8-1=7, c[8] = min(c[8], c[7] + 1); c[7]=inf, 所以c[8]仍为inf…
*当k=7,6,5,4,3,*结果同上
当k=2, k - a[i]=2-1=1, c[2] = min(c[2], c[1] + 1); c[1]=1, c[2]=inf, 所以c[2]更新为2,表示当你只有面值为1的硬币且只有两个时,凑出金额为2的钱所需最少硬币数为2
当k=1同理可得,c[1]不变

此时c[9] ={ 0, 1, 2, inf, inf, inf, inf, inf, inf};

至此已经用完2个面值为1的硬币进行更新(遍历完b[i]次了)

  • 再用面额为2的硬币(i=1)

有3个面额为2的硬币,先用第1个(j=0), 更新c[9].
当k=8, k - a[i]=8-2=6, c[8] = min(c[8], c[6] + 1); c[6]=inf, 所以c[8]仍为inf…
*当k=7,6,5,*结果同上
当k=4, k - a[i]=4-2=2, c[4] = min(c[4], c[2] + 1); c[2]=2, c[4]=inf, 所以c[4]更新为3,表示当你有2个面值为1的硬币且只有1个面值为2的硬币时,凑出金额为4的钱所需最少硬币数为3
当k=3,k - a[i]=3-2=1,c[3] = min(c[3], c[1] + 1); c[1]=1, c[3]=inf, 所以c[3]更新为2,表示当你有2个面值为1的硬币且只有1个面值为2的硬币时,凑出金额为3的钱所需最少硬币数为2
当k=2, k - a[i]=2-2=0,c[1] = min(c[1], c[0] + 1); c[0]=1, c[2]更新为1
当k=1, k < a[i]=2, 所以要凑出金额k不能用面值a[i]的硬币,太大了,所以退出循环。

此时c[9] ={ 0, 1, 1, 2, 3, inf, inf, inf, inf};

继续挥霍第2个面值为2的硬币(j=1), 同理
当k=8,7, c[8、7]仍为inf
当k=6,k - a[i]=6-2=4, c[6] = min(c[6], c[4] + 1); c[4]=3, c[6]=inf, 所以c[6]更新为4
.。。。。。。
更新后c[9] ={ 0, 1, 1, 2, 2, 3, 4, inf, inf};
。。。。。。
。。。。。。
最终结果输出c[8]=2;

代码:

#include<iostream>

#define INF 65535

using namespace std;

int main()
{
    
    
	int n,m;
	int a[100],b[100];
	cout << "输入面值个数" << endl;
    cin >> n;
    int sum=0;
    int c[100];//数组 c[]存放要找的最少硬币个数
    for(int i=0;i<n;i++){
    
    
		cout << "输入面值及个数" << endl;
        cin >> a[i] >> b[i];
        sum+=a[i]*b[i];
    }
	cout << "输入要找零金额" << endl;
    cin>>m;
    
    if(sum < m) 
		cout << "-1" << endl;       //问题无解
    else{
    
    
		// 初始化
		c[0] = 0;
        for (int i = 1; i <= m; i++)
            c[i] = INF;
		// 先用a[i]币值,遍历b[i]次,每次表示用1个a[i]币值的硬币
		for (int i = 0; i < n; i++)  // 用于遍历面值,将每个面值都代入一次,更新最小值
		{
    
    
			for (int j = 0; j <= b[i]; j++)   // 
			{
    
    
				for (int k = m; k >= a[i]; k--)
					c[k] = min(c[k], c[k - a[i]] + 1);
			}
		}
		cout << "==========================" << endl;
		if(c[m] != INF)
			cout << "最少硬币数:" << c[m]<<endl;
		else
			cout << "-1" <<endl;
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/zoey_peak/article/details/122638952
今日推荐