(算法1-动态规划-学习整理存档)
1.问题:
给定一个整数数组 int coins[] 表示不同面额的硬币, int amount表示总金额,返回找零所需的最少硬币数量。如果总金额为0,可以由0枚硬币组成。如果这笔钱不能由任何硬币组成,返回-1。硬币数量无限制。
输入:硬币面值(种类不限),总金额
输出:找零所需的最少硬币数量
Problem:
2.算法思路
问题分析:
硬币找零求最优解问题,可由若干交叠的子问题构成,且符合递推关系。贪心算法只能选择局部最优,所以并不适用;递归算法考虑了整体最优,可以解决问题,但穷举所有组合的计算效率太低;而动态规划算法,可以减少重复运算,并得到全局最优解。因此,本问题宜选取动态规划算法解决。
动态规划实现思路:
1)状态:找出子问题与原问题之间会发生变化的变量,这个变量就是状态转移方程的参数。
在硬币找零问题中,此变量为剩余兑换金额。
2)状态转移方程:F[i] 表示兑换金额为 i 时,需要的最少硬币数, 边界为 i =amount ;
F(i)= 0, n=0;
F(i)= -1, n<0;
F(i)= min( F(i), F(n-coins[j])+1 )
其中 coin[j] 表示第j种硬币的面值。
3)初始状态:动态规划是由已知的子问题递推到更大问题上去,因此需要一个初始状态作为原点,成为计算的开端;
在本问题中,初始状态为 amount=0 时 F(0)=0。
4)执行状态转移:改变状态,让状态不断接近初始状态。
在本问题中,选择一枚硬币,用其凑零钱,即可改变状态。
5)返回最终结果。
3.算法复杂度分析
时间效率:O(nm)
空间效率:θ(n)
其中n为兑换总金额,m为硬币面值种类。
算法可表示为表格,每行有n个格子表示F(n), 每一格的计算需要求出至多m个数的最小值。
4.源代码
#include <iostream>
#include<vector>
using namespace std;
/* 动态规划
1.设计状态
2.写出状态转移方程
3.设定初始状态
4.执行状态转移
5.返回最终的解
*/
/*算法函数*/
int MinCoinNum(vector<int>coins,int amount,int coin_type)
{
vector<int>coinNum;//存储i面值,找零所需硬币个数
vector<int>coinUsed;//存储所用的硬币
//初始化
for (int k = 0; k <= amount; k++)
{
coinNum.push_back(0);
coinUsed.push_back(0);
}
//特殊情况0
if (0 == amount)
{
return 0;
}
//递推开始
for (int i = 1; i <= amount; i++)
{
int coin_min = INT_MAX;//i面值,需要硬币个数
int coin_last = 0;//当前使用的硬币
//限制硬币面值
for (int j = 0; j < coin_type && coins[j] <= i; j++)
{
//判断i-coins[j]能否找开
if (i == coins[j] || coinUsed[i - coins[j]] != 0)
{
coin_min = (coinNum[i - coins[j]] + 1) <= coin_min ? coinNum[i - coins[j]] + 1 : coin_min;
coin_last = coins[j];//更新
}
}
coinNum[i] = coin_min;
coinUsed[i] = coin_last;
}
if (0 == coinUsed[amount])//找不开
{
return -1;
}
else
{
return coinNum[amount];
}
}
int main()
{
vector<int>coins;
int amount;
int coin_type = 0;
cout << "Please input denominations." << endl;
int tp;
while (cin >> tp )
{
coins.push_back(tp);
coin_type++;
if (cin.get() == '\n')
break;
}
cout << "Please input amount." << endl;
cin >> amount;
cout << "Result:" << endl;
cout << MinCoinNum(coins, amount, coin_type) << endl;
return 0;
}