持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情
题目描述
这里有 n 个一样的骰子,每个骰子上都有 k 个面,分别标号为 1 到 k 。
给定三个整数 n , k 和 target ,返回可能的方式(从总共 kn 种方式中)滚动骰子的数量,使正面朝上的数字之和等于 target 。
答案可能很大,你需要对 109 + 7 取模 。
注意:
每种料理只能制作一次。
示例 1:
输入:n = 1, k = 6, target = 3 输出:1 解释:你扔一个有6张脸的骰子。 得到3的和只有一种方法。
示例 2:
输入:n = 2, k = 6, target = 7 输出:6 解释:你扔两个骰子,每个骰子有6个面。 得到7的和有6种方法1+6 2+5 3+4 4+3 5+2 6+1。
记忆化递归
记忆化搜索是解决重复问题行之有效的方法,通过一个数组记录所有已经到达的结果,避免递归穷举过程中的重复运算。具体实现步骤如下:
- 设置终止条件:当只剩一枚骰子时,如果remain是可以被掷出的(1 ≤ remain ≤ k),那么找return 1,表示找到了一种成功的掷法;反之return 0。相当于在target总数的target - 1个缝隙中,插入d - 1个隔板,使得每一个分区的大小都在[1,f]内,如果出现某个分区在[1,f]范围外的,这一次分区的数目记为0即可。
- 中间进行剪枝:当还剩下dices枚骰子时,最多能掷出dices * k,如果remain过大,则可以提前结束
class Solution {
int [][] memo;
public int numRollsToTarget(int d, int f, int target) {
this.memo = new int [d+1][target+1];
for(int i=0;i<=d;i++){
Arrays.fill(memo[i], -1);
}
return dfs(d, f, target);
}
private int dfs(int d,int f,int target){
if(d<0||target<0){
return 0;
}
if(d==0&&target==0) return 1;
if(memo[d][target]!=-1) return memo[d][target];
long res = 0;
for(int i=1;i<=f;i++){
res+= dfs(d-1, f, target-i);
}
res = res%1000000007 ;
memo[d][target] = (int)res;
return (int)(res%1000000007);
}
}
最后
其实这道题目用动态规划也可以做,顺便也可以通过动态规划+记忆化结合的方法。利用
dp[i][j]表示用i个骰子得出j的组合数。dp[i][j] = dp[i-1][j-1] + ... + dp[i-1][j-f] if (j-f >= 0)。甚至可以优化为1维,因为每次更新都只与上一次相关。注意点:
- n个骰子出现n的情况只有一种,那就是全为1;一个骰子有f个面,则骰出j(j属于f)的只有j朝上,一种情况。
- 枚举每个骰子的点数k,根据上一层的计算结果得到方案数
- n个骰子的最小就是n个都是1向上,最小值为n。
最后的方程:
dp[i][j] += dp[i - 1][j - l]其中1 <= l <= min(j, k)