硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)
示例1:
输入: n = 5
输出:2
解释: 有两种方式可以凑成总金额:
5=5
5=1+1+1+1+1
示例2:
输入: n = 10
输出:4
解释: 有四种方式可以凑成总金额:
10=10
10=5+5
10=5+1+1+1+1+1
10=1+1+1+1+1+1+1+1+1+1
这个就算是一个比较裸的完全背包求方案数的问题,和完全背包基本类似,唯一区别就是dp[i][j]代表用前i个物品正好组成j的方案数。
value[i]代表第i个物品的价值,很容易想到对于每个dp[i][j],我们可以去枚举使用多少个i物品,假设使用了k个i物品,那么dp[i][j]就加上了dp[i-1][j-k*value[i]],这个还是很好理解的。
但是这样的问题在于速度太慢,有很多的重复计算,举个例子,也是通俗的讲一下完全背包转移方程的推导过程。
假设要计算dp[5][10],,当前value[5] = 3,我们可以选择放1个,2个或者3个五号物品(0个五号物品先不考虑因为0个物品并没有什么重复计算),我们现在选择放2个五号物品,那么dp[5][10]应该加上dp[4][4]的方案数,这个应该是很显然的。
那么我们思考一下计算dp[5][7]的时候,我们也肯定是考虑过放1个五号物品,那么dp[5][7]肯定也加过dp[4][4]的方案数,这就是问题的关键,我们没必要在回到dp[4][4]了,因为dp[5][7]中已经包含的dp[4][4]!
所以对于dp[i][j],我们不去考虑要放多少个i号物品,而是换个角度思考问题,只要是用前i个物品凑成了j-value[i]的价值,那么我再放一个i号物品就凑成j的价值,所以dp[i][j]要加上dp[i][j-value[i]],转移方程就是这样得出来的。
class Solution {
public int waysToChange(int n) {
int mod = 1000000007;
int rule[] = {1,5,10,25};
int dp[][] = new int [4][n+1];
for(int i=0;i<4;i++)
{
dp[i][0] = 1;
for(int j=1;j<=n;j++)
{
if(i>0)
dp[i][j] = dp[i-1][j];
if(j>=rule[i])
dp[i][j] = (dp[i][j]+dp[i][j-rule[i]])%mod;
}
}
return dp[3][n];
}
}