1155 Number of Dice Rolls With Target Sum 掷骰子的N种方法
描述
这里有 d
个一样的骰子,每个骰子上都有 f
个面,分别标号为 1, 2, ..., f。
我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和
。
如果需要掷出的总点数为 target
,请你计算出有多少种不同的组合情况(所有的组合情况总共有f^d
种),模10^9 + 7
后返回。
- 示例 1:
输入:d = 1, f = 6, target = 3
输出:1
- 示例 2:
输入:d = 2, f = 6, target = 7
输出:6
- 示例 3:
输入:d = 2, f = 5, target = 10
输出:1
- 示例 4:
输入:d = 1, f = 2, target = 3
输出:0
- 示例 5:
输入:d = 30, f = 30, target = 500
输出:222616187
- 提示:
1 <= d, f <= 30
1 <= target <= 1000
思路
这明显是个动态规划问题
- 规律公式
d
为骰子数, f
为骰子面数, target
为目标值, k
表示当前第d个骰子掷出的数
- 初始状态
需要投掷的目标数在单个骰子的范围内 返回1
, 否则返回0
代码实现
递归实现
class Solution {
static int MOD = (int)(1e9+7);
public int numRollsToTarget(int d, int f, int target) {
if (target < d | target > d*f) return 0;
int[][] dp = new int[d+1][target+1];
// 初始化 全-1 表示未被处理
for (int[] dpi : dp) Arrays.fill(dpi, -1);
return helper(d, f, target, dp);
}
int helper(int d, int f, int target, int[][] dp) {
// target 超出骰子的投掷范围
if (target < d | target > d*f) return 0;
// 如果已经处理过 使用之前记录数据
if (dp[d][target] != -1) return dp[d][target];
// 当单个骰子时
if (d==1) {
if (target <= f && target > 0) return dp[d][target]=1;
else return dp[d][target]=0;
}
// 数据没有被记录 骰子数大于等于2
int ans = 0;
// 对当前骰子投掷出的每个面数都进行处理
for (int cur=1; cur <= f; cur++) {
ans = (ans + helper(d-1, f, target-cur, dp))%MOD;
}
// 对数据进行记录
return dp[d][target]=ans;
}
}
非递归实现
class Solution {
// 取模数 1e9 + 7
final static int MOD = 1000000007;
public int numRollsToTarget(int d, int f, int target) {
if (target < d | target > d*f) return 0;
// dp 表示当骰子为i(1~d)时j(1~target)的组合数
// 其实长度可以是2 因为只使用上一轮的数据
int[][] dp = new int[d+1][target+1];
// 初始为1
dp[0][0] = 1;
// 从有1个骰子开始 1~d
for (int i = 1; i <= d; i++) {
// 当前骰子投掷数 1~f
for (int cur = 1; cur <= f; cur++) {
// 加上上一轮没有投掷cur时prev的组合数
for (int prev = 0; prev+cur <= target; prev++) {
dp[i][prev+cur] = (dp[i][prev+cur] + dp[i-1][prev])%MOD;
}
}
}
return dp[d][target];
}
}