/* * 假设我们有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. * 问总共有多少种可能的组合方式? * 2、华为面试题 * 1分2分5分三种硬币,组成已一角/共有多少解法 * 3.cc150 给出 1 5 10 25 有多少种方法 * * 思路: 1、输入总价,返回一个函数包含(总价,数组,最大的下标) * 2、判断下标是否等于0 则返回1 * 3、定义有有多少种解法 进行一个for循环 */
此问题简单的来想,可以有明确的递推关系。我们可以选择用递归来做。(首先讲递推,递归在后面)
思路:
假定现已给定四种面值,给定n。
我们可以对于每种面值的硬币,从大到小,选择要或者不要。
如果要剩下多少,如果不要剩下多少,将剩下的钱进行下一次递归,同时换一种面值的硬币。
这样就可以算出各种情况下要凑齐n所需的解法。
- 如果要凑的n为0,那么无论用那种面值,都是一种解法
- 如果用的面值为1,无论n为多少,都是一种凑法
- 当n小于5时,无论用多少种面值(1必须要),都是一种凑法
下图为每种n对应的用不同硬币的凑法,四行从上到下对应1,5,10,25面值,每一列为从1到n。具体的解释都在代码注释里。
#include<stdio.h> #include<string.h> //#include<iostream> //using namespace std; const int coins[4] = {1, 5, 10, 25}; int main(){ int n; scanf("%d",&n); const int N = n; int a[4][N+1]; memset(a,0,sizeof(a)); for(int i = 0; i < 4; i++) a[i][0] = 1; //面值为0的凑出来的部分,初始化为1 for(int i = 0; i <= n; i++) a[0][i] = 1; //用面值为1凑出来的都初始化为1 for(int i = 1; i < 4; i++){ // 硬币的种类 for(int j = 1; j <= n; j++){ // 总金额 for(int k = 0; k*coins[i] <= j; k++){// 在选择k个当前最大面值的硬币情况下的种类数 a[i][j] += a[i-1][j-k*coins[i]]; // x+y+z+1(在选择了k个当前面值的情况下,剩余的总金额的解法) } } } // for(int i = 0; i < 4; i++){ // for(int j = 0; j <= n; j++){ // cout << a[i][j] << " "; // } // cout << endl; // } printf("%d\n",a[3][n]); return 0; }
由这个思路,我们可以利用递归,减小因开辟二维数组带来的空间开销。
对于当前的状态,我们都可以转化为是否要当前面值、要几张的解法,加上是否要下一个面值……一直到最后。
#include<stdio.h> const int coins[4] = {1, 5, 10, 25}; int countWaysCore(int n, int cur){ //n表示输入的要凑的数,cur为coins的下标,用于递归时选择当前面值 //cur每次递归递减,出口条件为cur=0 if(cur == 0) return 1; //如果cur=0,只有一种凑法 int res = 0; //如果不是0,用res记录要返回的结果 for(int i = 0; i*coins[cur] <= n; i++){ //此循环表示是否选当前面值的硬币,选几张。 int left = n-i*coins[cur]; //left表示剩余的n,用于下一次递归 res += countWaysCore(left,cur-1); //res记录当前解法,递归完所有层,res迭代为总解法 } return res; } int countWays(int n){ if(n <= 0) return 0; return countWaysCore(n,3); } int main(){ int n; while(~scanf("%d",&n)){ printf("%d\n",countWays(n)); } return 0; }