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.
Two sequences are considered different if at least one element differs from each other. Since the answer may be too large, return it modulo 10^9 + 7
.
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
Constraints:
1 <= n <= 5000
rollMax.length == 6
1 <= rollMax[i] <= 15
Dynamic Programming Solution 1
State definition: dp[i][j]: the total number of different sequences of i rolls with the the last number being j. Init: dp[1][j] = 1, for j in [0, 5]. (1-indexed mapped to 0-indexed). Answer: sum of dp[n][j] for j in [0, 5].
State transition analysis: for dp[i][j], there can be two cases: either the last rolled number is different with the previous number or is the same with the previous one.
For the 1st case, simply iterate j from 0 to 5 and add all dp[i - 1][k] where k != j to dp[i][j].
For the 2nd case, we know that there are already two consecutive rolled numbers of j. We can have at most rollMax[j] consecutive j. Since rollMax[j] is at most 15, we can just add all the sequences that end with 2, 3, 4... rollMax[j] consecutive j.
A few key points in doing so:
1. for a fixed consecutive number of j, say cnt, we need to add all dp[i - cnt][t] where t != j. This represents all sequences that ends with cnt exact consecutive j.
2. There are two scenarios that terminate the sum. Either we exceed the limit of rollMax[j] or we run out of rolls, i.e, the current number of rolls is <= rollMax[j]. If exceeding rollMax[j], simply stop; if we are out of rolls, we need to add 1 to dp[i][j] to count the all js in all i rolls case. This is because when we have 0 roll, we don't have other 5 numbers that are different with j to choose from. It is just one sequence that only has j in it.
The runtime is O(n * 6 * 15 * 6). space complexity is O(n * 6).
Mistakes made: when summing the final result res, use res = (res + dp[n][i]) % mod; res += dp[n][i] % mod; is incorrect because it does not apply modular operation on the updated res, it only applies % on each individual dp[n][i].
class Solution { public int dieSimulator(int n, int[] rollMax) { int mod = (int)1e9 + 7; long dp[][] = new long[n + 1][6]; long res = 0; Arrays.fill(dp[1], 1); for(int i = 2; i <= n; i++) { for(int j = 0; j < 6; j++) { for(int k = 0; k < 6; k++) { if(j != k) { dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod; } else { int cnt = 2; for(; cnt <= rollMax[j] && cnt < i; cnt++) { for(int t = 0; t < 6; t++) { if(t == j) { continue; } dp[i][j] = (dp[i][j] + dp[i - cnt][t]) % mod; } } if(cnt <= rollMax[j] && cnt == i) { dp[i][j] = (dp[i][j] + 1) % mod; } } } } } for(int i = 0; i < 6; i++) { res = (res + dp[n][i]) % mod; } return (int)res; } }
Dynamic programming solution 2.
State: Declare a 3D array: dp[2][6][16]. The 1st dimension is just a rolling index as we'll use previous computation result to calcuate the current iteration of 1 more dice roll. The 2nd dimension represents the last rolled number and the 3rd dimenstion represents the consecutive time of the last rolled number. Here the 3rd dimension is from 0 to 15 inclusive. 0 means that the last roll yields a different number, so the current number is not the last rolled number, thus its consecutive time is 0.
Each time we have 1 more dice roll, we compute the new 2D array that represents the current state of all possible sequences counts. So the outer loop goes from 2 to n. Inside we do the following:
1. reinitialize the new 2D array that will be computed.
2. for each number i of consecutive time j, if the 1 more roll generates a number t that is not i, dp[(k + 1) % 2][t][1] += dp[k % 2][i][j]; if t == i, then if we haven't exceeded the limit of rollMax[t], dp[(k + 1) % 2][t][j + 1] += dp[k % 2][t][j].
Init: for the 1st roll, there is 1 sequence for each number of consecutive time 1.
Answer: All sequences must end at a number j from 1 to 6, with from 0 to 15 consecutive time of j. So the final answer is just the sum of last computed 2D array.
class Solution { public int dieSimulator(int n, int[] rollMax) { int mod = (int)1e9 + 7; long res = 0; long[][][] dp = new long[2][6][16]; //init: the 1st roll with a rolled number of consecutive time 1 for(int i = 0; i < 6; i++) { dp[0][i][1] = 1; } //dp[(k + 1) % 2][i][j] has 1 more roll than dp[k % 2][i][j] for(int k = 2; k <= n; k++) { //re-init dp[(k + 1) % 2] before computing it for(int p = 0; p < 6; p++) { Arrays.fill(dp[(k + 1) % 2][p], 0); } for(int i = 0; i < 6; i++) { for(int j = 0; j <= 15; j++) { //the 1 more roll can be any number for(int t = 0; t < 6; t++) { if(t != i) { dp[(k + 1) % 2][t][1] = (dp[(k + 1) % 2][t][1] + dp[k % 2][i][j]) % mod; } else if(j + 1 <= rollMax[t]) { dp[(k + 1) % 2][t][j + 1] = (dp[(k + 1) % 2][t][j + 1] + dp[k % 2][t][j]) % mod; } } } } } //sum up all sequences long[][] resDp = dp[(n + 1) % 2]; for(int i = 0; i < 6; i++) { for(int j = 0; j <= 15; j++) { res = (res + resDp[i][j]) % mod; } } return (int)res; } }