【每日一题Day114】LC1223 掷骰子模拟 | 记忆化搜索+dp

简介: 【每日一题Day114】LC1223 掷骰子模拟 | 记忆化搜索+dp

掷骰子模拟【LC1223】

有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。

不过我们在使用它时有个约束,就是使得投掷骰子时,连续 掷出数字 i 的次数不能超过 rollMax[i]i 从 1 开始编号)。

现在,给你一个整数数组 rollMax 和一个整数 n,请你来计算掷 n 次骰子可得到的不同点数序列的数量。

假如两个序列中至少存在一个元素不同,就认为这两个序列是不同的。由于答案可能很大,所以请返回 10^9 + 7 之后的结果。

啊啊啊激动 自己写出来了!!!

记忆化搜索+dp
  • 思路:
  • 由于方案数与当前为第几次掷骰子、前一个骰子值及其连续次数相关,并且整个回溯过程是有大量重复递归调用的,因此可以采用记忆化搜索和dp的方法实现

image.png实现

  • 初始时,将dp数组置为-1
  • f函数的搜索过程为:
  1. 如果已经掷完所有骰子,即i==1时,那么返回1
  2. 如果当前状态已经搜索过,那么返回dp数组中的值
  3. 如果以上情况都不符合,那么枚举本次骰子可能的值,并将可能的方案数记录在dp数组中
  • 如果骰子值与前一次相同,那么需要判断连续的次数是否小于rollMax
  • 如果骰子值与前一次不相同,那么更改骰子值,并将次数置为1,继续搜索
class Solution {
    public static final int MOD = (int)(1e9 + 7);
    int[][][] dp;
    int[] rollMax;
    int n;
    public int dieSimulator(int n, int[] rollMax) {
        this.n = n;
        this.rollMax = rollMax;
        int max = 0;
        for (int roll : rollMax){
            max = Math.max(roll, max);
        }
        dp = new int[n][6][max + 1];
        for (int i = 0; i < dp.length; i++){
            for (int j = 0; j < dp[0].length; j++){
                 Arrays.fill(dp[i][j], -1);// 没有访问过
            }         
        }
        return f(0, -1, 1);
    }
    public int f(int i, int pre, int count){
        if (i == n) return 1;
        if (pre >= 0 && count >= 0 && dp[i][pre][count] != -1) return dp[i][pre][count];
        int res = 0;
        for (int num = 0; num < 6; num++){
            if (num == pre && count >= rollMax[num]) continue;
            res = (res + f(i + 1, num, num == pre ? count + 1 : 1) ) % MOD;
        }
        if (pre >= 0 && count >= 0) dp[i][pre][count] = res;
        return res;
    }
}

image.png

dp数组的形式

定义状态 d[i][j][k]表示已经完成了 i次掷骰子,第 i次掷的是 j,并且已经连续掷了 k次 j的合法序列数。

class Solution {
    private static final long MOD = (long) 1e9 + 7;
    public int dieSimulator(int n, int[] rollMax) {
        int m = rollMax.length; // 6
        var f = new int[n][m][15];
        for (int j = 0; j < m; ++j)
            Arrays.fill(f[0][j], 1);
        for (int i = 1; i < n; ++i)
            for (int last = 0; last < m; ++last)
                for (int left = 0; left < rollMax[last]; ++left) {
                    long res = 0;
                    for (int j = 0; j < m; ++j)
                        if (j != last) res += f[i - 1][j][rollMax[j] - 1];
                        else if (left > 0) res += f[i - 1][j][left - 1];
                    f[i][last][left] = (int) (res % MOD);
                }
        long ans = 0;
        for (int j = 0; j < m; ++j)
            ans += f[n - 1][j][rollMax[j] - 1];
        return (int) (ans % MOD);
    }
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/dice-roll-simulation/solutions/2103292/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-sje6/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。









目录
相关文章
|
4月前
【每日一题Day242】LC1262可被三整除的最大和 | 贪心 dp
【每日一题Day242】LC1262可被三整除的最大和 | 贪心 dp
44 0
|
4月前
【每日一题Day297】LC1444切披萨的方案数 | 动态规划+二维前缀和
【每日一题Day297】LC1444切披萨的方案数 | 动态规划+二维前缀和
54 0
|
4月前
【每日一题Day302】LC849到最近的人的最大距离 | 贪心+分类讨论
【每日一题Day302】LC849到最近的人的最大距离 | 贪心+分类讨论
48 0
|
4月前
【每日一题Day331】LC2560打家劫舍 IV | 二分查找 + 贪心
【每日一题Day331】LC2560打家劫舍 IV | 二分查找 + 贪心
34 0
|
4月前
【每日一题Day211】LC1079活字印刷 | 回溯 计数dp
【每日一题Day211】LC1079活字印刷 | 回溯 计数dp
49 0
|
4月前
【每日一题Day126】LC1140石子游戏Ⅱ | 博弈dp 记忆化搜索
【每日一题Day126】LC1140石子游戏Ⅱ | 博弈dp 记忆化搜索
45 0
|
4月前
【每日一题Day262】LC1911最大子序列交替和 | dp
【每日一题Day262】LC1911最大子序列交替和 | dp
34 0
|
4月前
【每日一题Day329】LC213打家劫舍Ⅱ | 动态规划
【每日一题Day329】LC213打家劫舍Ⅱ | 动态规划
33 0
|
4月前
【每日一题Day330】LC337打家劫舍Ⅲ | 动态规划
【每日一题Day330】LC337打家劫舍Ⅲ | 动态规划
31 0
|
4月前
【每日一题Day298】LC13883n 块披萨 | 动态规划之打家劫舍
【每日一题Day298】LC13883n 块披萨 | 动态规划之打家劫舍
43 0