硬币。给定数量不限的硬币,币值为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
说明:
0 <= n (总金额) <= 1000000
点击此处跳转题目。
使用数学方法解答,将 n 看作是 25、10、5 和 1 的组合,即 n = 25a + 10b + 5c + d
。因为给定 n 后,d 由 a、b、c 决定,因此仅讨论 a、b、c 之间的关系即可。
将 a、b、c 降序排列,其关系如下所示:
给定 a 不动,讨论 b 和 c 的组合数。
共计 (b + 1)(b + c +1) 种。
a 减 1,则多出 25 分,总面值为 25 + 10b + 5c。由 b、c 重新降序排列,即10b’ + 5c’ = 25 + 10b + 5c,解得 b’ = b + (5 + c) / 2,c’ = (c + 5) % 2。
public class Solution {
public int WaysToChange(int n) {
int a = n / 25, b = n % 25 / 10, c = n % 25 % 10 / 5;
return (int)Partition(a, b, c);
}
public long Partition(int a, int b, int c) {
if (a < 0) return 0;
long n = (b + 1L) * (b + c + 1L) % 1000000007; // 使用 1L 进行 long 运算防止结果溢出
return (Partition(a - 1, b + (c + 5) / 2, (c + 5) % 2) + n) % 1000000007;
}
}
使用动态规划解答,记 f(i,j)
表示用前 i
种硬币构成面值 j
的种类数,因此有
f ( i , j ) = f ( i − 1 , j ) + f ( i − 1 , j − c i ) + f ( i − 1 , j − 2 ⋅ c i ) + ⋯ + f ( i − 1 , j − k ⋅ c i ) f(i,j)=f(i-1,j)+f(i-1,j-c_i)+f(i-1,j-2\cdot c_i)+\cdots+f(i-1,j-k\cdot c_i) f(i,j)=f(i−1,j)+f(i−1,j−ci)+f(i−1,j−2⋅ci)+⋯+f(i−1,j−k⋅ci)
其中,c_i
表示第 i 种硬币的面值,k = j / c_i
。
进行优化:
i
、i - 1
有关,类似斐波那契数列的求解,只需要一个一维数组存储数据即可,数组内容不断更新 1 、 2 、 ⋯ 、 i 1、2、\cdots、i 1、2、⋯、i 对应的值。事实上,动态规划的解法思想和数学解法很类似,即 c 1 , c 2 , c 3 , c 4 c_1,c_2,c_3,c_4 c1,c2,c3,c4 对应于数学解法的 d , c , b , a d,c,b,a d,c,b,a。
public class Solution {
public int WaysToChange(int n) {
int[] coins = { 1, 5, 10, 25 };
int[] f = new int[n + 1];
f[0] = 1;
for (int i = 0; i < 4; i++)
for (int j = coins[i]; j < n + 1; j++) {
f[j] += f[j - coins[i]];
f[j] %= 1000000007;
}
return f[n];
}
}
说明:f[j] += f[j - coins[i]]
为化简写法,即 f[j] = f[j] + f[j - coins[i]]
,对应于 f ( i , j ) = f ( i − 1 , j ) + f ( i , j − c i ) f(i,j)=f(i-1,j)+f(i,j-c_i) f(i,j)=f(i−1,j)+f(i,j−ci)。
动态规划的方法没有数学解法快,这里的解释是: