问题描述
假设我们有8种不同面值的硬币{1,2,5,10,20,50,100,200},用这些硬币组合够成一个给定的数值n。 例如n=200,那么一种可能的组合方式为 200 = 3 * 1 + 1*2 + 1*5 + 2*20 + 1 * 50 + 1 * 100. 问总共有多少种可能的组合方式?
问题分析
假如只有面值1的硬币,那么组成任何数值都只有1种组合方法。
假如有1和2两种面值得硬币,那么首先可以分为两种情况,一个是使用面值2的硬币,一个是不使用面值2的硬币,使用面值为2的硬币又可细分为使用几枚面值为2的硬币。
假如总值为5,即可分为使用0枚面值2的硬币,使用1枚面值2的硬币,使用2枚面值2的硬币,这样便将问题分为3个子问题,用面值1的硬币组合总数值为(5 - 0 * 2) = 5,(5 - 1 * 2) = 3,(5 - 2 * 2) = 1,由上述可知这三种情况的组合方法已经得出,总数值为5的用1和2两枚硬币的组合方法有1+1+1 = 3种。
如果再加上面值为5的的硬币,则可以细分成使用0枚面值5的硬币,使用1枚面值5的硬币,使用0枚面值5的硬币的组成方法前面已经求出为3种,使用1枚面值5的硬币只有1种组成方法,因此总方法为3+1=4种。
由上诉可以得出,每一个数值都可像这样细分成多个子问题求解,下图即是每一个数值在不同面值硬币下的方法递推图,从小到大一步一步得出结果,。每一个数值所分成的子问题都是在前面已经得出的结果,只需相加即可
代码实现
1.递推法
import java.util.Scanner;
public class CoinCombination {
static int[] money = {1, 2, 5, 10, 20, 50, 100, 200};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(combine(n));
}
private static int combine(int n) {
//使用二维数组缓存递推过程
//行表示面值情况,列表示每一种数值的组成方法
int[][] dp = new int[8][n + 1];
//初始化,任何种面值组成总数值为0都只有1种组成方法
for (int i = 0; i < 8; i++) {
dp[i][0] = 1;
}
//初始化,只有面值1的任何数值都只有1种组成方法
for (int i = 0; i < n + 1; i++) {
dp[0][i] = 1;
}
//计算每一个数值在每一种面值情况下的组成方法数
for (int i = 1; i < 8; i++) {
for (int j = 1; j < n + 1; j++) {
//循环遍历使用了k枚该面值硬币后的组成方法数
for (int k = 0; k * money[i] <= j; k++) {
//将问题拆成子问题再相加
dp[i][j] += dp[i - 1][j - k * money[i]];
}
}
}
//返回所需要的数值组成方法
return dp[7][n];
}
}
2.递归法
该方法利用了上述思维,但是代码更简洁
import java.util.Scanner;
public class CoinCombination {
static int[] money = {1, 2, 5, 10, 20, 50, 100, 200};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(combine(n,7));
}
private static int combine(int n,int k) {
if (n < 0) {
return 0;
}
if (n == 0) {
return 1;
}
int cut = 0;
//循环遍历使用了k枚该面值硬币后的组成方法数再相加
for (int i = 0; k >= 0 && i * money[k] <= n; i++) {
//每一层递归都在拆分成更小的问题,直到只有面值1的硬币
cut += combine1(n - i * money[k], k - 1);
}
return cut;
}
}
3.暴力破解法
当然,最容易想到的自然是暴力破解,但是时间复杂度较高,不建议使用
import java.util.Scanner;
public class CoinCombination {
static int[] money = {1, 2, 5, 10, 20, 50, 100, 200};
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println(combine(n));
}
private static int combine(int n) {
int cut = 0;
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= n / 2; j++) {
for (int k = 0; k <= n / 5; k++) {
for (int l = 0; l <= n / 10; l++) {
for (int m = 0; m <= n / 20; m++) {
for (int o = 0; o <= n / 50; o++) {
for (int p = 0; p <= n / 100; p++) {
for (int q = 0; q <= n / 200; q++) {
if (1 * i + 2 * j + 5 * k + 10 * l + 20 * m + 50 * o + 100 * p + 200 * q == n) {
cut ++;
}
}
}
}
}
}
}
}
}
return cut;
}
}