力扣 322.零钱兑换(动态规划问题)
题目:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
动态规划问题解题一般分为
1、明确问题
2、转移方程
3、初始条件和边界情况
4、计算顺序
我们按照这个步骤来对这道算法题进行解答
一、明确问题(简化定义)
题目说的是用最少多少硬币拼出amount(就是11),那么明确最优策略下,最后一枚硬币只可能是coin[i](i为coin数组下标){就是1和2和5},意思就是拼出最优解的最后一枚硬币一定是三种硬币中的一种,
那么因为是最优策略,所以必须让amount-coin[i](11-coin[i])最少。
那么问题就转化成了另一个问题:
最少用多少枚硬币可以拼出11-coin[i]
简化定义:
设状态f(x)=最少用多少枚硬币可以拼出x
- 如果coin[i]为1,那么f(11)=f(11-1)+1(最后加1是最后一枚硬币)
- 如果coin[i]为2,那么f(11)=f(11-2)+1
- 如果coin[i]为5,那么f(11)=f(11-5)+1
那么此时问题转换成了使得组成最后一枚硬币前面的硬币数量最少
二、转移方程
由上面可知现在问题转化成了
f(11)=min { f(11-1)+1,f(11-2)+1,f(11-5)+1}
f(11)为拼出11所需最少硬币数
f(11-1)为拼出10所需最少硬币数。。。
三、初始条件和边界情况
两个问题::x-1,x-2,x-5小于0怎么办?什么时候使递归停下来?
如果不能拼出x,就定义f(x)=无穷,例如f(-1),f(-2)=无穷
所以f(1)=min{f(0)+1,f(-1)+1,f(-4)+1}
初始条件:f(0)=0
初始条件是转移方程算不出来的,只能通过自己定义,所以说边界条件很重要。
四、计算顺序
α为无穷
f(0) | f(1) | f(2) | f(3) | f(4) | f(5) | f(6) | f(7) | f(8) | ||
---|---|---|---|---|---|---|---|---|---|---|
α | α | 0 | 1 | 2 | 2 | 2 | 3 | 2 | 3 | 3 |
这些都可以通过以下公式一步一步算出来,并且我们在算f(n)的时候f(n)前面的都已经算出来了
f(11)=min { f(11-1)+1,f(11-2)+1,f(11-5)+1}
上面就是全部的求解过程
以下是java代码
package test;
import java.util.Scanner;
/**
* @author zhazhalin
* @version 1.0
* @date 2021/4/6 21:05
*/
/**
* 257三枚硬币拼出27,问有多少种拼法
*/
public class Coin {
public static void main(String[] args) {
int[] a = {2, 5, 7};
int f = f(a, 27);
System.out.println(f);
}
public static int f(int[] a, int sum) {
//首先定义边界值
int[] f = new int[sum + 1];
int n = a.length;
f[0] = 0; //定义边界条件
for (int i = 1; i <= sum; ++i) {
f[i] = Integer.MAX_VALUE; //将数组中的数定义为无穷大
for (int j = 0; j < n; ++j) {
//i>a[j]要满足i-a[j]必须有足够的钱才能继续向前推导,此处的a[j]就是对应的257三种硬币
if (i >= a[j] && f[i - a[j]] != Integer.MAX_VALUE) { //并且必须确保
//当我们计算到f[sum]的时候,前面的数值都已经计算过了,这就是动态规划的记忆性
f[i] = Math.min(f[i - a[j]] + 1, f[i]);
}
}
}
if (f[sum]==Integer.MAX_VALUE){
return -1;
}
return f[sum];
}
}