Dice Roll Simulation
Problem
A die simulator generates a random number from 1 to 6 for each roll. You introduced a constraint to the generator such that it cannot roll the number i more than rollMax[i] (1-indexed) consecutive times.
Given an array of integers rollMax and an integer n, return the number of distinct sequences that can be obtained with exact n rolls. Since the answer may be too large, return it modulo 109 + 7.
Two sequences are considered different if at least one element differs from each other.
Examples
Example 1
Input: n = 2, rollMax = [1,1,2,2,2,3]
Output: 34
Explanation: There will be 2 rolls of die, if there are no constraints on the die, there are 6 * 6 = 36 possible combinations. In this case, looking at rollMax array, the numbers 1 and 2 appear at most once consecutively, therefore sequences (1,1) and (2,2) cannot occur, so the final answer is 36-2 = 34.
Example 2
Input: n = 2, rollMax = [1,1,1,1,1,1]
Output: 30
Example 3
Input: n = 3, rollMax = [1,1,1,2,2,3]
Output: 181
Solution
Method 1 – Dynamic Programming (Top-Down with Memoization)
Intuition
We use dynamic programming to count the number of valid sequences. The key is to track, for each roll, which number was last rolled and how many times it has appeared consecutively. By memoizing the state (rolls_left, last_num, last_count), we avoid redundant calculations.
Approach
- Define a recursive function
dp(rolls_left, last_num, last_count):
rolls_left: Number of rolls remaining.last_num: The last number rolled (0 to 5 for dice faces).last_count: How many timeslast_numhas appeared consecutively.
- Base case: If
rolls_left == 0, return 1 (valid sequence). - For each possible next number (0 to 5):
- If
next_num == last_numandlast_count < rollMax[next_num], continue the streak. - If
next_num != last_num, start a new streak. - Skip if the streak would exceed
rollMax.
- Memoize results for each state to avoid recomputation.
- Sum all valid ways for all possible starting numbers.
Code
C++
class Solution {
public:
int dieSimulator(int n, vector<int>& rollMax) {
const int MOD = 1e9 + 7;
int dp[501][6][16] = {};
memset(dp, -1, sizeof(dp));
function<int(int, int, int)> dfs = [&](int left, int last, int cnt) -> int {
if (left == 0) return 1;
if (last != -1 && dp[left][last][cnt] != -1) return dp[left][last][cnt];
int ans = 0;
for (int i = 0; i < 6; ++i) {
if (i == last) {
if (cnt < rollMax[i])
ans = (ans + dfs(left - 1, i, cnt + 1)) % MOD;
} else {
ans = (ans + dfs(left - 1, i, 1)) % MOD;
}
}
if (last != -1) dp[left][last][cnt] = ans;
return ans;
};
return dfs(n, -1, 0);
}
};
Go
func dieSimulator(n int, rollMax []int) int {
mod := int(1e9 + 7)
dp := make([][][]int, n+1)
for i := range dp {
dp[i] = make([][]int, 7)
for j := range dp[i] {
dp[i][j] = make([]int, 16)
for k := range dp[i][j] {
dp[i][j][k] = -1
}
}
}
var dfs func(left, last, cnt int) int
dfs = func(left, last, cnt int) int {
if left == 0 {
return 1
}
if last != -1 && dp[left][last][cnt] != -1 {
return dp[left][last][cnt]
}
ans := 0
for i := 0; i < 6; i++ {
if i == last {
if cnt < rollMax[i] {
ans = (ans + dfs(left-1, i, cnt+1)) % mod
}
} else {
ans = (ans + dfs(left-1, i, 1)) % mod
}
}
if last != -1 {
dp[left][last][cnt] = ans
}
return ans
}
return dfs(n, -1, 0)
}
Java
class Solution {
public int dieSimulator(int n, int[] rollMax) {
int MOD = 1000000007;
Integer[][][] dp = new Integer[n+1][7][16];
return dfs(n, -1, 0, rollMax, dp, MOD);
}
private int dfs(int left, int last, int cnt, int[] rollMax, Integer[][][] dp, int MOD) {
if (left == 0) return 1;
if (last != -1 && dp[left][last][cnt] != null) return dp[left][last][cnt];
int ans = 0;
for (int i = 0; i < 6; i++) {
if (i == last) {
if (cnt < rollMax[i])
ans = (ans + dfs(left-1, i, cnt+1, rollMax, dp, MOD)) % MOD;
} else {
ans = (ans + dfs(left-1, i, 1, rollMax, dp, MOD)) % MOD;
}
}
if (last != -1) dp[left][last][cnt] = ans;
return ans;
}
}
Kotlin
class Solution {
fun dieSimulator(n: Int, rollMax: IntArray): Int {
val MOD = 1_000_000_007
val dp = Array(n+1) { Array(7) { IntArray(16) { -1 } } }
fun dfs(left: Int, last: Int, cnt: Int): Int {
if (left == 0) return 1
if (last != -1 && dp[left][last][cnt] != -1) return dp[left][last][cnt]
var ans = 0
for (i in 0..5) {
if (i == last) {
if (cnt < rollMax[i])
ans = (ans + dfs(left-1, i, cnt+1)) % MOD
} else {
ans = (ans + dfs(left-1, i, 1)) % MOD
}
}
if (last != -1) dp[left][last][cnt] = ans
return ans
}
return dfs(n, -1, 0)
}
}
Python
class Solution:
def dieSimulator(self, n: int, rollMax: list[int]) -> int:
MOD = 10**9 + 7
from functools import lru_cache
@lru_cache(maxsize=None)
def dp(left: int, last: int, cnt: int) -> int:
if left == 0:
return 1
ans = 0
for i in range(6):
if i == last:
if cnt < rollMax[i]:
ans = (ans + dp(left-1, i, cnt+1)) % MOD
else:
ans = (ans + dp(left-1, i, 1)) % MOD
return ans
return dp(n, -1, 0)
Rust
impl Solution {
pub fn die_simulator(n: i32, roll_max: Vec<i32>) -> i32 {
const MOD: i32 = 1_000_000_007;
let n = n as usize;
let mut dp = vec![vec![vec![-1; 16]; 7]; n+1];
fn dfs(left: usize, last: i32, cnt: usize, roll_max: &Vec<i32>, dp: &mut Vec<Vec<Vec<i32>>>) -> i32 {
if left == 0 { return 1; }
if last != -1 && dp[left][last as usize][cnt] != -1 {
return dp[left][last as usize][cnt];
}
let mut ans = 0;
for i in 0..6 {
if i as i32 == last {
if cnt < roll_max[i] as usize {
ans = (ans + dfs(left-1, i as i32, cnt+1, roll_max, dp)) % MOD;
}
} else {
ans = (ans + dfs(left-1, i as i32, 1, roll_max, dp)) % MOD;
}
}
if last != -1 {
dp[left][last as usize][cnt] = ans;
}
ans
}
dfs(n, -1, 0, &roll_max, &mut dp)
}
}
Complexity
- ⏰ Time complexity:
O(n * 6 * maxRoll)wheremaxRollis the maximum value inrollMax. - 🧺 Space complexity:
O(n * 6 * maxRoll)due to memoization.
Method 2 – Dynamic Programming (Bottom-Up Tabulation)
Intuition
We use bottom-up dynamic programming to build the solution iteratively. Instead of recursion, we fill a DP table where each entry represents the number of ways to reach a certain state (rolls left, last number, consecutive count). This avoids stack overhead and is often more efficient.
Approach
- Define a 3D DP array
dp[roll][face][count]:
roll: Number of rolls completed (from 1 to n).face: The current face (0 to 5).count: How many timesfacehas appeared consecutively (from 1 to rollMax[face]).
- Initialize
dp[1][face][1] = 1for all faces (first roll can be any face, count is 1). - For each roll from 2 to n:
- For each face:
- For each possible consecutive count:
- If continuing the same face, increment count (if allowed by rollMax).
- If switching to a new face, reset count to 1.
- Sum all valid ways for the last roll across all faces and counts.
- Return the result modulo 1e9+7.
Code
C++
class Solution {
public:
int dieSimulator(int n, vector<int>& rollMax) {
const int MOD = 1e9 + 7;
int dp[501][6][16] = {};
for (int f = 0; f < 6; ++f)
dp[1][f][1] = 1;
for (int r = 2; r <= n; ++r) {
for (int f = 0; f < 6; ++f) {
for (int pf = 0; pf < 6; ++pf) {
if (pf == f) continue;
for (int cnt = 1; cnt <= rollMax[pf]; ++cnt) {
dp[r][f][1] = (dp[r][f][1] + dp[r-1][pf][cnt]) % MOD;
}
}
for (int cnt = 2; cnt <= rollMax[f]; ++cnt) {
dp[r][f][cnt] = (dp[r][f][cnt] + dp[r-1][f][cnt-1]) % MOD;
}
}
}
int ans = 0;
for (int f = 0; f < 6; ++f)
for (int cnt = 1; cnt <= rollMax[f]; ++cnt)
ans = (ans + dp[n][f][cnt]) % MOD;
return ans;
}
};
Go
func dieSimulator(n int, rollMax []int) int {
mod := int(1e9 + 7)
dp := make([][][]int, n+1)
for i := range dp {
dp[i] = make([][]int, 6)
for j := range dp[i] {
dp[i][j] = make([]int, 16)
}
}
for f := 0; f < 6; f++ {
dp[1][f][1] = 1
}
for r := 2; r <= n; r++ {
for f := 0; f < 6; f++ {
for pf := 0; pf < 6; pf++ {
if pf == f {
continue
}
for cnt := 1; cnt <= rollMax[pf]; cnt++ {
dp[r][f][1] = (dp[r][f][1] + dp[r-1][pf][cnt]) % mod
}
}
for cnt := 2; cnt <= rollMax[f]; cnt++ {
dp[r][f][cnt] = (dp[r][f][cnt] + dp[r-1][f][cnt-1]) % mod
}
}
}
ans := 0
for f := 0; f < 6; f++ {
for cnt := 1; cnt <= rollMax[f]; cnt++ {
ans = (ans + dp[n][f][cnt]) % mod
}
}
return ans
}
Java
class Solution {
public int dieSimulator(int n, int[] rollMax) {
int MOD = 1_000_000_007;
int[][][] dp = new int[n+1][6][16];
for (int f = 0; f < 6; f++)
dp[1][f][1] = 1;
for (int r = 2; r <= n; r++) {
for (int f = 0; f < 6; f++) {
for (int pf = 0; pf < 6; pf++) {
if (pf == f) continue;
for (int cnt = 1; cnt <= rollMax[pf]; cnt++) {
dp[r][f][1] = (dp[r][f][1] + dp[r-1][pf][cnt]) % MOD;
}
}
for (int cnt = 2; cnt <= rollMax[f]; cnt++) {
dp[r][f][cnt] = (dp[r][f][cnt] + dp[r-1][f][cnt-1]) % MOD;
}
}
}
int ans = 0;
for (int f = 0; f < 6; f++)
for (int cnt = 1; cnt <= rollMax[f]; cnt++)
ans = (ans + dp[n][f][cnt]) % MOD;
return ans;
}
}
Kotlin
class Solution {
fun dieSimulator(n: Int, rollMax: IntArray): Int {
val MOD = 1_000_000_007
val dp = Array(n+1) { Array(6) { IntArray(16) } }
for (f in 0..5) dp[1][f][1] = 1
for (r in 2..n) {
for (f in 0..5) {
for (pf in 0..5) {
if (pf == f) continue
for (cnt in 1..rollMax[pf]) {
dp[r][f][1] = (dp[r][f][1] + dp[r-1][pf][cnt]) % MOD
}
}
for (cnt in 2..rollMax[f]) {
dp[r][f][cnt] = (dp[r][f][cnt] + dp[r-1][f][cnt-1]) % MOD
}
}
}
var ans = 0
for (f in 0..5)
for (cnt in 1..rollMax[f])
ans = (ans + dp[n][f][cnt]) % MOD
return ans
}
}
Python
class Solution:
def dieSimulator(self, n: int, rollMax: list[int]) -> int:
MOD = 10**9 + 7
dp: list[list[list[int]]] = [[[0]*16 for _ in range(6)] for _ in range(n+1)]
for f in range(6):
dp[1][f][1] = 1
for r in range(2, n+1):
for f in range(6):
for pf in range(6):
if pf == f:
continue
for cnt in range(1, rollMax[pf]+1):
dp[r][f][1] = (dp[r][f][1] + dp[r-1][pf][cnt]) % MOD
for cnt in range(2, rollMax[f]+1):
dp[r][f][cnt] = (dp[r][f][cnt] + dp[r-1][f][cnt-1]) % MOD
ans = 0
for f in range(6):
for cnt in range(1, rollMax[f]+1):
ans = (ans + dp[n][f][cnt]) % MOD
return ans
Rust
impl Solution {
pub fn die_simulator(n: i32, roll_max: Vec<i32>) -> i32 {
const MOD: i32 = 1_000_000_007;
let n = n as usize;
let mut dp = vec![vec![vec![0; 16]; 6]; n+1];
for f in 0..6 {
dp[1][f][1] = 1;
}
for r in 2..=n {
for f in 0..6 {
for pf in 0..6 {
if pf == f { continue; }
for cnt in 1..=roll_max[pf] as usize {
dp[r][f][1] = (dp[r][f][1] + dp[r-1][pf][cnt]) % MOD;
}
}
for cnt in 2..=roll_max[f] as usize {
dp[r][f][cnt] = (dp[r][f][cnt] + dp[r-1][f][cnt-1]) % MOD;
}
}
}
let mut ans = 0;
for f in 0..6 {
for cnt in 1..=roll_max[f] as usize {
ans = (ans + dp[n][f][cnt]) % MOD;
}
}
ans
}
}
Complexity
- ⏰ Time complexity:
O(n * 6 * maxRoll * 6), where maxRoll is the maximum in rollMax. - 🧺 Space complexity:
O(n * 6 * maxRoll)