leetcode
leetcode 1151 ~ 1200
掷骰子模拟

掷骰子模拟

难度:

标签:

题目描述

代码结果

运行时间: 58 ms, 内存: 17.4 MB


/*
 * Problem: We need to calculate the number of different sequences of dice rolls given the constraint
 * that each number i (1-6) cannot appear more than rollMax[i] times consecutively.
 * 
 * Approach: We will use dynamic programming to solve this problem. We will maintain a DP table dp[i][j][k]
 * where:
 * - i is the number of dice rolls
 * - j is the current number on the dice (1-6)
 * - k is the number of times the number j has appeared consecutively
 * The state transition will consider rolling a different number or continuing the streak of the current number
 * up to the limit specified in rollMax.
 * 
 * Note: Java Stream API is not very suitable for this type of problem since it's more functional in nature.
 * However, for demonstration purposes, we will use streams where applicable.
 */

import java.util.Arrays;
import java.util.stream.IntStream;

public class DiceRollSimulationStream {
    private static final int MOD = 1000000007;
    
    public int dieSimulator(int n, int[] rollMax) {
        int[][][] dp = new int[n + 1][6][16];
        IntStream.range(0, 6).forEach(i -> dp[1][i][1] = 1);
        
        IntStream.range(2, n + 1).forEach(roll -> {
            IntStream.range(0, 6).forEach(num -> {
                IntStream.range(0, 6).forEach(prevNum -> {
                    if (prevNum != num) {
                        IntStream.rangeClosed(1, rollMax[prevNum]).forEach(count -> {
                            dp[roll][num][1] = (dp[roll][num][1] + dp[roll - 1][prevNum][count]) % MOD;
                        });
                    } else {
                        IntStream.range(1, rollMax[num]).forEach(count -> {
                            dp[roll][num][count + 1] = (dp[roll][num][count + 1] + dp[roll - 1][num][count]) % MOD;
                        });
                    }
                });
            });
        });
        
        return IntStream.range(0, 6).flatMap(num -> 
            IntStream.rangeClosed(1, rollMax[num]).map(count -> dp[n][num][count])
        ).reduce(0, (a, b) -> (a + b) % MOD);
    }
}

解释

方法:

这个题解使用动态规划的方法来求解。定义 f[i][j] 为投掷了 i 次骰子,且第 i 次的结果为 j+1 的不同序列的数量。数组 s[i] 表示投掷 i 次骰子总共可能的不同序列数量。初始化第一次投掷的结果 f[0] = [1, 1, 1, 1, 1, 1],因为第一次投掷每个数字都只有一种可能。对于每次投掷 i > 0,我们通过计算排除不符合 rollMax 限制的序列数量来更新 f[i][j]。如果投掷次数大于 j+1 可以连续出现的次数 mx,则需要减去那些不合法的序列。总结算后,s[i] 是所有 f[i][j] 的和,最终 s[n-1] 就是答案。

时间复杂度:

O(n)

空间复杂度:

O(n)

代码细节讲解

🦆
在动态规划算法中,数组 f[i][j] 的定义是什么意义?为什么它能代表在第 i 次投掷后,结果为 j+1 的不同序列数量?
在动态规划中,数组 f[i][j] 代表了在完成第 i 次投掷后,且第 i 次的结果为 j+1 的不同序列的数量。这个定义允许我们将问题分解为更小、更易管理的子问题。每个 f[i][j] 不仅记录了达到当前投掷结果的方式,而且保留了前 i 次投掷中,最后一个数字是 j+1 的所有可能序列的数量。这种方式使我们能够基于前一次的结果来更新当前次的结果,从而有效地利用动态规划解决问题。
🦆
为什么在计算 f[i][j] 时,当 i > mx 时需要减去 `s[i - mx - 1] - f[i - mx - 1][j]` 这个表达式的值?这个表达式的具体意义是什么?
当 i 大于 mx(骰子面 j+1 可以连续出现的最大次数)时,我们需要减去那些不合法的序列,即连续出现 j+1 超过 mx 次的序列。表达式 `s[i - mx - 1] - f[i - mx - 1][j]` 表示从前 i-mx-1 次投掷的总序列数中减去那些第 i-mx-1 次投掷结果为 j+1 的序列数。这样做是因为这些序列在接下来的 mx 次投掷中如果都选择 j+1,将导致超过了允许的最大连续次数,因此需要排除这部分不合法的序列。
🦆
在题解中提到当 `i == mx` 时,需要减去 1 来调整序列总数。这里的 1 指的是什么情况?为什么是减去 1 而不是其他数字?
当 `i == mx` 时,减去的 1 代表的是那个唯一的序列,其中所有的投掷结果都是 j+1,并且恰好连续出现了 mx 次。这是因为在达到 mx 次连续投掷时,唯一违反规则的序列就是所有投掷都选 j+1 的序列,因此我们只需要减去这一种情况。没有其他序列在这种投掷次数条件下违反了连续出现的最大次数限制。
🦆
如何理解题解中使用 `s[i] = sum(f[i]) % MOD` 这一步来计算当前所有可能的序列总数?这里的 sum 函数是如何工作的,以及它为什么能给出正确的结果?
在题解中使用 `s[i] = sum(f[i]) % MOD` 这一步是为了计算在完成第 i 次投掷后所有可能的不同序列总数。sum(f[i]) 是计算数组 f[i] 中所有元素的和,即将所有结束于不同骰子面的序列数量加在一起,从而得到总的序列数量。取模运算 MOD 是用来防止计算结果溢出,保持数值在一个可处理的范围内。这样,每次我们只关注有效的序列总数,确保计算的正确性和效率。

相关问题