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

掷骰子模拟【LC1223】

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

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

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

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

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

记忆化搜索+dp
  • 思路:

    • 由于方案数与当前为第几次掷骰子、前一个骰子值及其连续次数相关,并且整个回溯过程是有大量重复递归调用的,因此可以采用记忆化搜索和dp的方法实现
    • 定义 f ( i , p r e , c o u n t ) f(i,pre,count) f(i,pre,count) 表示第 i − 1 i-1 i1次骰子值为 p r e pre pre,连续掷出 p r e pre pre的次数为 c o u n t count count时,按照限制再掷 n-i 次骰子可得到的不同点数序列的数量,那么 f ( 0 , − 1 , 0 ) f(0,-1,0) f(0,1,0) 即为最终结果
    • 使用dp数组记录已经搜索过的方案数, d p [ i ] [ p r e ] [ c o u n t ] dp[i][pre][count] dp[i][pre][count]数组的含义同f函数,为第 i − 1 i-1 i1次骰子值为 p r e pre pre,连续掷出 p r e pre pre的次数为 c o u n t count count时,按照限制掷完所有骰子可得到的不同点数序列的数量。
  • 实现

    • 初始时,将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;
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n m 2 k ) O(nm^2k) O(nm2k),其中 m m mrollMax 的长度,即 6。 k k krollMax数组的最大值,状态的个数为 n m k nmk nmk,那么动态规划的时间复杂度 = 状态个数 × 单个状态的转移个数,因此时间复杂度为 O ( n m S ) O(nmS) O(nmS)
      • 空间复杂度: O ( n m k ) O(nmk) O(nmk)
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)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值