目录
动态规划适用于:最大/小值,可不可行,计数问题 这类集中场景
本题,求解是否可以由指定面值的硬币完成兑换且统计最少需要的硬币的数量,可以使用动态规划来求解。
动态规划
动态规划是可以将大问题分解成子问题,利用子问题的结果来求解大问题,所以首先要确定状态。
要从问题的最后一步和子问题的角度开始思考
如果能完全找零,那么假设最后一个找零的硬币的面值为k,则问题由找出11金额的最少硬币数量的问题,变成找出11-k的最少硬币数量。
下一步再分,假设11-k金额的的最后一个硬币的金额是k1,下一个子问题就是找出11-k-k1金额的最少硬币数量。
以此类推...
最后,就能找出一个金额的最少硬币数量。
可以确定子问题的状态:f(i)={组成金额为i的最少硬币数量}
状态转移方程:f(i)=1+f(i-ak)
该问题还是需要考虑提供的硬币的种类,对于不同面值的硬币都需要去考虑
f(i)=min{ f(i-1)+1, f(i-2)+1, f(i-5)+1 }
如果要找的金额是1,2,3,4....那么都是需要分析的,同时还有金额0,当然金额不可能是负数。
所以对于一个最基本的问题而言,会出现i-ak=0,1,2,3...的情况,因此,整个问题的最初始状态就是从f(0)开始,依次向后,同时需要就题和提供的条件来具体分析
边界条件就是最大金额本身,超过了最大金额也没有计算的必要。
所以就是从f(0) 到 f(amount)。
确定完所有的条件后,还需要考虑一下递归的问题
从这个状态转移方程而言,可以看出来使用最基本的递归来完成最小数量的计算。
不过,显而易见,递归实现会导致时间复杂度飙升
根据递归的推导图,递归过程中出现了大量的重复计算,这个非常消耗空间和时间资源。
直接递归会造成大量重复计算和数据冗余
动态规划的一个优势就是利用子问题的结果,然后计算下一个大问题的结果。所以不能直接使用递归,要考虑保存较小金额的最少硬币数量的计算结果,改变计算顺序。
不用从最后一个硬币的金额开始分析,可以从金额0开始向后计算和分析。这样当计算较大金额的时候,就可以直接得到f(i-k)子问题的结果。
同时还有一个关键的问题,就是所求金额和提供的硬币的面值的问题。从前向后进行状态转移计算时,会出现i-ak<0的情况,我们知道,当出现这种情况说明从这个硬币面值向后计算是无法得到结果的,就选用哦考虑还一个硬币的面值去计算,如果出现所有的i-ak<0的情况,就说明这个数量的金额是无法通过提供的硬币得到最小的硬币数量。同时,后面如果计算到当前金额时,也算无法计算的。我们可以考虑设置一个数值/标志位来表示,不存在最小硬币数量。
当f(i-ak)的值小于0时,就不进行计算,然后继续向查找其余硬币的面额是否满足当前金额,如果提供的硬币的面值都不满足条件,那就说明,此情况下,不存在最小找零的硬币数量。
为利方便求解最小的、存在的硬币数量,设置标志位为INT_MAX,方便计算。
在从0到amount的向后计算过程中,还需要对每一个提供的硬币的金额进行考虑计算,同时还要考虑子问题的存在问题。
- class Solution {
- public:
- int coinChange(vector<int>& coins, int amount) {
- int n=coins.size();//额定的硬币的种类
- //最后要求amount的最小数量
- //从动态规划的数组向后计算f(i),找零i金额最少需要多少数量的硬币
- int* v = new int[amount+1];
- v[0]=0;
- //从逻辑上是从0到amount+1的数组的计算
- for(int i=1;i<=amount;i++)
- {
- v[i]=INT_MAX;
- //遍历提供的面额,找出可行的最大的面额
- for(int j=0;j
- {
- //i-coins[j]是说明如果i金额减去指定的面额可以得到子问题i-ak的金额
- if(i-coins[j]>=0&&v[i-coins[j]]!=INT_MAX)
- {
- v[i]=min(v[i],v[i-coins[j]]+1);
- }
- }
- }
- if(v[amount]==INT_MAX)
- {
- v[amount]=-1;
- }
- return v[amount];
- }
- };