LeetCode破解之掷骰子的N种方法

294 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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)