Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1223. Dice Roll Simulation - Leetcode Solution

Problem Description

The Dice Roll Simulation problem asks you to determine how many different sequences of dice rolls are possible, given certain constraints. Specifically:

  • You have a standard 6-sided die (faces numbered 1 to 6).
  • You are given an integer n representing the total number of dice rolls.
  • You are also given an array rollMax of length 6, where rollMax[i] is the maximum number of times that face i+1 can appear consecutively in a sequence.

Your task is to count the number of possible sequences of length n such that no face appears more than its allowed consecutive times. The answer should be returned modulo 10^9 + 7.

Key constraints:

  • Each roll can be any face from 1 to 6, but consecutive appearances of the same face are limited by rollMax.
  • You must count all valid sequences, not just one.
  • There may be large values of n (up to 5000), so efficiency matters.

Thought Process

At first glance, you might think to generate all possible dice roll sequences of length n and count the valid ones. However, this would be extremely slow due to the exponential number of possibilities (6n).

The main challenge is to efficiently ensure that no face appears more than its allowed consecutive times. This suggests keeping track of:

  • How many times the last face was rolled consecutively.
  • Which face was rolled last.
  • How many rolls are left.
Since the same subproblems can occur repeatedly, dynamic programming (DP) is a natural fit. We can use DP to store and reuse results for subproblems defined by the current position, last face rolled, and its consecutive count.

Solution Approach

We use dynamic programming to solve this problem efficiently. The main idea is to define a DP state that captures all necessary information to make decisions:

  • State: dp[roll][last][count] represents the number of valid sequences of length roll, ending with face last (0-based), where last has appeared count times consecutively at the end.
  • Transition: For each possible next face, if it is the same as the last face, we can only use it if count < rollMax[last]. If it is a different face, we reset the consecutive count to 1.
  • Initialization: For the first roll, any face can be chosen with a consecutive count of 1.
  • Final Answer: Sum all dp[n][last][count] for all faces and all valid counts.

To optimize space, we can use two DP arrays (current and previous) or a 3D DP array of size n x 6 x max(rollMax).

  1. Initialize the DP array for the first roll.
  2. For each subsequent roll, update the DP array based on the previous state and the rules.
  3. After all rolls, sum up all valid states for the answer.

This approach ensures we never compute the same subproblem twice and always respect the constraints on consecutive rolls.

Example Walkthrough

Let's consider n = 3 and rollMax = [1,1,1,2,2,3].

  1. On the first roll, we can choose any face (1 to 6).
  2. On the second roll, if we rolled face 1 last time, we cannot roll face 1 again (since rollMax[0]=1). So, we can only roll faces 2-6.
  3. On the third roll, suppose we rolled 4 on the second roll, and it was the first time (since rollMax[3]=2), we can choose 4 again (for a consecutive count of 2), or any other face (resetting the count).

The DP tracks, for each step, which faces are allowed and how many times they have appeared consecutively. By the end, we sum all valid sequences of length 3.

Time and Space Complexity

Brute-force approach: Would be O(6n), since every roll has 6 choices, which is infeasible for large n.

DP approach:

  • There are n rolls, 6 possible last faces, and up to max(rollMax) consecutive counts.
  • So, total DP states are O(n * 6 * max(rollMax)).
  • For each state, we consider up to 6 transitions, so time complexity is O(n * 6 * max(rollMax) * 6), but since max(rollMax) is at most 15, this is efficient.
  • Space complexity is O(n * 6 * max(rollMax)), but can be reduced to O(6 * max(rollMax)) by reusing arrays.

Summary

The Dice Roll Simulation problem is a classic example where dynamic programming shines. By carefully defining a state that tracks the number of rolls, the last face, and its consecutive count, we efficiently count all valid sequences. The approach avoids repeated work and respects all constraints, making it both elegant and practical for large inputs.

Code Implementation

MOD = 10 ** 9 + 7

class Solution:
    def dieSimulator(self, n: int, rollMax: List[int]) -> int:
        dp = [[[0] * 16 for _ in range(6)] for _ in range(n + 1)]
        for i in range(6):
            dp[1][i][1] = 1
        for roll in range(2, n + 1):
            for last in range(6):
                for count in range(1, rollMax[last] + 1):
                    if dp[roll - 1][last][count]:
                        for nxt in range(6):
                            if nxt == last:
                                if count + 1 <= rollMax[last]:
                                    dp[roll][last][count + 1] = (dp[roll][last][count + 1] + dp[roll - 1][last][count]) % MOD
                            else:
                                dp[roll][nxt][1] = (dp[roll][nxt][1] + dp[roll - 1][last][count]) % MOD
        res = 0
        for last in range(6):
            for count in range(1, rollMax[last] + 1):
                res = (res + dp[n][last][count]) % MOD
        return res
      
class Solution {
public:
    int dieSimulator(int n, vector<int>& rollMax) {
        const int MOD = 1e9 + 7;
        int dp[n + 1][6][16] = {};
        for (int i = 0; i < 6; ++i)
            dp[1][i][1] = 1;
        for (int roll = 2; roll <= n; ++roll) {
            for (int last = 0; last < 6; ++last) {
                for (int count = 1; count <= rollMax[last]; ++count) {
                    if (dp[roll - 1][last][count]) {
                        for (int nxt = 0; nxt < 6; ++nxt) {
                            if (nxt == last) {
                                if (count + 1 <= rollMax[last])
                                    dp[roll][last][count + 1] = (dp[roll][last][count + 1] + dp[roll - 1][last][count]) % MOD;
                            } else {
                                dp[roll][nxt][1] = (dp[roll][nxt][1] + dp[roll - 1][last][count]) % MOD;
                            }
                        }
                    }
                }
            }
        }
        int res = 0;
        for (int last = 0; last < 6; ++last)
            for (int count = 1; count <= rollMax[last]; ++count)
                res = (res + dp[n][last][count]) % MOD;
        return res;
    }
};
      
class Solution {
    public int dieSimulator(int n, int[] rollMax) {
        int MOD = 1000000007;
        int[][][] dp = new int[n + 1][6][16];
        for (int i = 0; i < 6; ++i)
            dp[1][i][1] = 1;
        for (int roll = 2; roll <= n; ++roll) {
            for (int last = 0; last < 6; ++last) {
                for (int count = 1; count <= rollMax[last]; ++count) {
                    if (dp[roll - 1][last][count] > 0) {
                        for (int nxt = 0; nxt < 6; ++nxt) {
                            if (nxt == last) {
                                if (count + 1 <= rollMax[last])
                                    dp[roll][last][count + 1] = (dp[roll][last][count + 1] + dp[roll - 1][last][count]) % MOD;
                            } else {
                                dp[roll][nxt][1] = (dp[roll][nxt][1] + dp[roll - 1][last][count]) % MOD;
                            }
                        }
                    }
                }
            }
        }
        int res = 0;
        for (int last = 0; last < 6; ++last)
            for (int count = 1; count <= rollMax[last]; ++count)
                res = (res + dp[n][last][count]) % MOD;
        return res;
    }
}
      
var dieSimulator = function(n, rollMax) {
    const MOD = 1e9 + 7;
    let dp = Array.from({length: n + 1}, () =>
        Array.from({length: 6}, () => Array(16).fill(0))
    );
    for (let i = 0; i < 6; ++i) dp[1][i][1] = 1;
    for (let roll = 2; roll <= n; ++roll) {
        for (let last = 0; last < 6; ++last) {
            for (let count = 1; count <= rollMax[last]; ++count) {
                if (dp[roll - 1][last][count]) {
                    for (let nxt = 0; nxt < 6; ++nxt) {
                        if (nxt === last) {
                            if (count + 1 <= rollMax[last])
                                dp[roll][last][count + 1] = (dp[roll][last][count + 1] + dp[roll - 1][last][count]) % MOD;
                        } else {
                            dp[roll][nxt][1] = (dp[roll][nxt][1] + dp[roll - 1][last][count]) % MOD;
                        }
                    }
                }
            }
        }
    }
    let res = 0;
    for (let last = 0; last < 6; ++last)
        for (let count = 1; count <= rollMax[last]; ++count)
            res = (res + dp[n][last][count]) % MOD;
    return res;
};