问题描述
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
思路分析
1、确定状态
简单的说,就是解动态规划时需要开一个数组,数组的每个元素f[i]或者f[i][j]代表什么,类似解数学题中,xyz代表什么一样,具体分为下面两个步骤:
-------研究最优策略的最后一步
-------化为子问题
2、转移方程
根据子问题定义直接得到
3、初始条件和边界情况
初始条件一般都是a[0]、a[1]这种,多看看
边界条件主要是看数组的边界,数组越不越界
4、计算顺序
利用之前的计算结果
详细过程:
1、确定状态
虽然我们不知道最优策略是什么,但是最优策略一定是k枚硬币a1,a2…ak加起来等于11
所以一定有一枚最后的硬币:ak
除掉这枚硬币,前面硬币的面值相加起来是11-ak,所以就将原问题转化为了子问题:
原问题:最少用多少枚硬币拼出11(k枚)
子问题:最少用多少枚硬币拼出11-ak(k-1枚)
经过这两歩,得出状态:f[X]=最少用多少枚硬币拼出X
2、转移方程
设状态f[X]=最少用多少枚硬币拼出X
转移方程如下
f[x]=min{f[x-1]+1,f[x-2]+1,f[x-5]+1}
其中f[x]为拼出x最少需要的硬币数,f[x-1]+1为拼出x-1最少的硬币数,再加上最后一枚硬币1,以此类推。
3、初始条件和边界情况
边界条件x-1,x-2,x-5,小于0时,应该进行处理,这种情况其实就是拼不出来的情况,定义为正无穷初始条件一般就是根据转移方程计算不出来的值,从转移方程变量为0或1来选,根据题目进行分析,这个题目的初始条件就是f[0]=1,代入公式的话应该f[0]为正无穷,显然错误,所以自己定义f[0]=0,保证每次计算f[x]时用到的f[x-1],f[x-2]等都是已经计算过的。
4、计算顺序
这个题目应该是正序的,当我们计算到f[x]时,f[x-1],f[x-2],f[x-5]都已经得到结果了
总结:
每一次尝试三种硬币,一共11步;
与递归算法相比,没有任何重复计算;
算法时间复杂度(需要进行的步数):11* 3(amount * coinsSize)
代码示例
int min_fun(int a, int b)
{
int c = a<b?a:b;
return c;
}
int coinChange(int* coins, int coinsSize, int amount){
//1、确定状态
int f[amount+1];
//初始化
f[0] = 0;
//计算顺序 从小到大 i表示需要凑的总数
for(int i = 1;i<=amount;i++)
{
f[i] = INT_MAX;
for(int j = 0;j<coinsSize;j++)
{
//3.边界情况 f[i-coins[j]]!=INT_MAX 表示凑不出硬币时,而且INT_MAX相加可能还会溢出
if(i>=coins[j] && f[i-coins[j]]!=INT_MAX)
{
//2、转移方程
f[i] = min_fun(f[i],f[i-coins[j]]+1);
}
}
}
//当凑不出时,f[amount]仍然为INT_MAX,我们需要赋值为-1,最后返回f[amount]
if(f[amount] == INT_MAX)
f[amount] = -1;
return f[amount];
}
官网链接
力扣链接: https://leetcode.cn/