- 难度:
中等
- 本题涉及算法:
动态规划(dp)
- 思路:
完全背包问题
题目 面试题 08.11. 硬币
硬币。给定数量不限的硬币,币值为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
解题思路
由于无限数量硬币的选择,应该是一个完全背包问题
-
dp 数组建立:dp[i][j] 表示 i 种硬币组成面值为 j 时的方法数
-
考虑 base case
-
dp[0][j] 0 种硬币组成面值 j,不可能有方案,因此是 0, java 初始化时数组是 0,不用特殊处理
-
dp[i][0] 多种硬币组成面值 0,只有一种方案,就是一枚也不选
-
-
状态转移方程:
-
dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i])
-
其中 dp[i - 1][j] 表示当前硬币不选,那么由 i - 1 种组成面值 j
-
dp[i][j - coins[i]) 表示当前硬币选了,那么还需要组成面额为 j - coins[i], 这都是已要组成的面值大于当前硬币值为前提的。
-
因此可以先写出一个二维 dp 的代码,再进一步进行优化,状态压缩
代码 二维 dp
public int waysToChange(int n) {
int[] coins = new int[]{1, 5, 10, 25};
// 1. dp 表示对应的方法数 比如 dp[i][j] 表示 i 种硬币组成面值为 j 时的方法数
// 2. 多开一个位置,0 空着不用 在下面对dp[0][j] dp[i][0] 做特殊处理
int[][] dp = new int[5][n + 1];//
// 对dp[0][j] dp[i][0] 做特殊处理 且dp[0][j] 不存在
for (int i = 1; i <= 4; i++)
dp[i][0] = 1;
for (int i = 1; i <= 4; i++) { // i表示 i种币值,正好也对于coins
for (int j = 1; j <= n; n++) {// j表示 组成的面值
if (j - coins[i - 1] < 0) // 要组成的面值比当前硬币金额小,该硬币不可以选择
dp[i][j] = dp[i - 1][j] % 1000000007; // 只能由 i - 1 中硬币来组成面值 j
else
dp[i][j] = (dp[i - 1][j] + dp[i][j - coins[i - 1]]) % 1000000007;
}
}
return dp[4][n];
}
代码 一维 dp
public int waysToChange(int n) {
int[] coins = new int[]{1, 5, 10, 25};
int[] dp = new int[n + 1];
dp[0] = 1;
for (int coin : coins) {
for (int i = 1; i <= n; i++) {
if (i - coin >= 0) {
dp[i] = (dp[i] + dp[i - coin]) % 1000000007; // sum = sum+ 1 第一个sum 和第一个sum 相差一个状态
}
}
}
return dp[n];
}
PREVIOUS面试题 17.16. 按摩师