Number of Distinct Roll Sequences
HardPrompt
You are given an integer n. You roll a fair 6-sided dice n times. Determine the total number of distinct sequences of rolls possible such that the following conditions are satisfied:
- The greatest common divisor of any adjacent values in the sequence is equal to
1. - There is at least a gap of
2rolls between equal valued rolls. More formally, if the value of theithroll is equal to the value of thejthroll, thenabs(i - j) > 2.
Return the total number of distinct sequences possible. Since the answer may be very large, return it modulo 109 + 7.
Two sequences are considered distinct if at least one element is different.
Example 1:
Input: n = 4
Output: 184
Explanation: Some of the possible sequences are (1, 2, 3, 4), (6, 1, 2, 3), (1, 2, 3, 1), etc.
Some invalid sequences are (1, 2, 1, 3), (1, 2, 3, 6).
(1, 2, 1, 3) is invalid since the first and third roll have an equal value and abs(1 - 3) = 2 (i and j are 1-indexed).
(1, 2, 3, 6) is invalid since the greatest common divisor of 3 and 6 = 3.
There are a total of 184 distinct sequences possible, so we return 184.Example 2:
Input: n = 2
Output: 22
Explanation: Some of the possible sequences are (1, 2), (2, 1), (3, 2).
Some invalid sequences are (3, 6), (2, 4) since the greatest common divisor is not equal to 1.
There are a total of 22 distinct sequences possible, so we return 22.
Constraints:
1 <= n <= 104
Approaches
2 approaches with complexity analysis and trade-offs.
This problem exhibits optimal substructure and overlapping subproblems, making it a good candidate for dynamic programming. A straightforward way to implement a DP solution is using recursion with memoization (a top-down DP approach). We define a recursive function that calculates the number of valid sequences from a given state, and we store the results of subproblems in a memoization table to avoid recomputing them.
Algorithm
- The state of our recursive function can be defined by
solve(index, prev_roll, prev_prev_roll), which calculates the number of ways to complete a valid sequence from the currentindexgiven the two preceding rolls. - We use a 3D array,
memo[n][7][7], to store the computed results for each state to avoid redundant calculations. - Base Case: When
index == n, it means we have successfully constructed a valid sequence of lengthn. We return 1. - Recursive Step: For the current
index, we iterate through all possible die rolls (currfrom 1 to 6). For eachcurr, we check if it's a valid next roll:gcd(curr, prev_roll) == 1(ifprev_rollexists).curr != prev_rollandcurr != prev_prev_roll(to satisfy the gap constraint).
- If
curris valid, we make a recursive callsolve(index + 1, curr, prev_roll)and add its result to our total count (modulo10^9 + 7). - The result for the state
(index, prev_roll, prev_prev_roll)is stored in the memoization table before being returned. - The process is initiated by calling
solve(0, 0, 0, n), where 0 is a placeholder for non-existent rolls.
Walkthrough
The state of our recursive function can be defined by (index, prev_roll, prev_prev_roll), representing the number of ways to complete a valid sequence from the current index given the two preceding rolls.
index: The current roll position we are trying to fill (from 0 to n-1).prev_roll: The value of the roll atindex-1.prev_prev_roll: The value of the roll atindex-2.
We use a 3D array, memo[n][7][7], to store the computed results. The dimension size 7 is used to handle dummy values (like 0) for non-existent previous rolls at the beginning of the sequence.
The function works as follows:
- Base Case: When
index == n, it means we have successfully constructed a valid sequence of lengthn. We return 1. - Recursive Step: For the current
index, we iterate through all possible die rolls (1 to 6). For each potential rollcurr, we check if it's valid with respect toprev_rollandprev_prev_roll. Ifcurris a valid roll, we make a recursive callsolve(index + 1, curr, prev_roll)and add its result to our total count. The final count for the state(index, prev_roll, prev_prev_roll)is stored in the memoization table before being returned.
class Solution { int MOD = 1_000_000_007; int[][][] memo; int[][] gcd_cache; private int gcd(int a, int b) { if (gcd_cache[a][b] != 0) return gcd_cache[a][b]; int result = b == 0 ? a : gcd(b, a % b); return gcd_cache[a][b] = gcd_cache[b][a] = result; } public int distinctSequences(int n) { memo = new int[n + 1][7][7]; gcd_cache = new int[7][7]; for (int i = 0; i <= n; i++) { for (int j = 0; j <= 6; j++) { Arrays.fill(memo[i][j], -1); } } return solve(0, 0, 0, n); } private int solve(int index, int prev1, int prev2, int n) { if (index == n) { return 1; } if (memo[index][prev1][prev2] != -1) { return memo[index][prev1][prev2]; } long count = 0; for (int curr = 1; curr <= 6; curr++) { if (curr == prev1 || curr == prev2) { continue; } if (prev1 != 0 && gcd(curr, prev1) != 1) { continue; } count = (count + solve(index + 1, curr, prev1, n)) % MOD; } return memo[index][prev1][prev2] = (int) count; }}Complexity
Time
O(n * 7 * 7 * 6) = O(n). There are O(n) states, and each state's computation involves a loop of size 6.
Space
O(n * 7 * 7) = O(n). This is for the memoization table. The recursion stack also contributes O(n) space in the worst case.
Trade-offs
Pros
Conceptually simpler to derive from the problem's recursive nature.
Correctly solves the problem within the given constraints.
Cons
Higher space complexity compared to the optimized iterative approach due to the memoization table.
May lead to a
StackOverflowErrorfor very largen(thoughn=10^4is usually acceptable in modern environments).Recursive function calls introduce some performance overhead.
An alternative to the top-down approach is a bottom-up iterative solution. This approach builds the solution from the smallest subproblem (sequences of length 2) up to the desired length n. By carefully analyzing the state transitions, we can observe that computing the results for length i only requires the results from length i-1. This allows for a significant space optimization, reducing the space complexity from O(n) to O(1).
Algorithm
- Define
dp[j][k]as the number of valid sequences of the current length ending with rollkfollowed by rollj. - Handle the base case
n=1separately. - Precompute a
coprime[7][7]table for O(1) GCD checks. - Initialize the
dptable for sequences of length 2.dp[j][k] = 1for all valid pairs(k, j)wherej != kandgcd(j, k) == 1. - Iterate for length
ifrom 3 ton:- Create a
next_dp[7][7]table for lengthi. - Calculate
sums[k], the total number of valid sequences of lengthi-1ending with rollk. This is done by summingdp[k][l]over alll. - For each potential last two rolls
(k, j)of a lengthisequence:- If
jandkform a valid pair:- The number of ways is the count of valid sequences of length
i-1ending inkwhose second-to-last roll was notj. This is calculated as(sums[k] - dp[k][j]). - Store this result in
next_dp[j][k].
- The number of ways is the count of valid sequences of length
- If
- After iterating through all
jandk, replacedpwithnext_dp.
- Create a
- After the loop, the total number of sequences of length
nis the sum of all values in the finaldptable.
Walkthrough
We define a DP state dp[j][k] as the number of valid sequences of the current length ending with the roll k followed by the roll j. The key idea is to build up the solution iteratively.
The state transition to compute the counts for length i from i-1 is as follows: A sequence of length i-1 ending in (l, k) can be extended with a new roll j if j is valid with respect to k and l. The conditions are gcd(j, k) == 1, j != k, and j != l. The number of ways to form a sequence ending in (k, j) is the sum of ways to form sequences of length i-1 ending in (l, k) for all l != j.
This sum can be computed efficiently. Let S[k] be the total number of valid sequences of length i-1 ending with roll k. Then the number of sequences of length i ending in (k, j) is S[k] - dp[k][j] (the total minus the cases where the roll before k was j).
Since the calculation for length i only depends on length i-1, we only need to store the DP table for the previous length. This reduces space complexity to be constant.
class Solution { public int distinctSequences(int n) { if (n == 1) { return 6; } int MOD = 1_000_000_007; boolean[][] coprime = new boolean[7][7]; for (int i = 1; i <= 6; i++) { for (int j = 1; j <= 6; j++) { if (gcd(i, j) == 1) { coprime[i][j] = true; } } } long[][] dp = new long[7][7]; // Base case: sequences of length 2 for (int j = 1; j <= 6; j++) { for (int k = 1; k <= 6; k++) { if (j != k && coprime[j][k]) { dp[j][k] = 1; } } } // Iterate for lengths 3 to n for (int i = 3; i <= n; i++) { long[][] next_dp = new long[7][7]; long[] sums = new long[7]; // Calculate total sequences of length i-1 ending with k for (int k = 1; k <= 6; k++) { for (int l = 1; l <= 6; l++) { sums[k] = (sums[k] + dp[k][l]) % MOD; } } // Calculate dp for length i for (int j = 1; j <= 6; j++) { for (int k = 1; k <= 6; k++) { if (j != k && coprime[j][k]) { // Sum of sequences of length i-1 ending in k, where prev roll was not j long count = (sums[k] - dp[k][j] + MOD) % MOD; next_dp[j][k] = count; } } } dp = next_dp; } long total = 0; for (int j = 1; j <= 6; j++) { for (int k = 1; k <= 6; k++) { total = (total + dp[j][k]) % MOD; } } return (int) total; } private int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }}Complexity
Time
O(n * 6^2) = O(n). The main loop runs `n-2` times. Inside, the operations take O(6*6 + 6*6) which is constant time. Thus, the total time complexity is linear in `n`.
Space
O(1). We use a few arrays of constant size (e.g., `7x7`), so the space required does not depend on `n`.
Trade-offs
Pros
Highly efficient in both time and space.
Constant space complexity makes it suitable for very large
n.Avoids recursion overhead and potential stack depth limits.
Iterative solutions can have better performance in practice due to factors like cache locality.
Cons
Can be slightly more complex to formulate and implement correctly compared to the recursive solution.
Solutions
Solution
class Solution {public int distinctSequences(int n) { if (n == 1) { return 6; } int mod = (int)1 e9 + 7; int[][][] dp = new int[n + 1][6][6]; for (int i = 0; i < 6; ++i) { for (int j = 0; j < 6; ++j) { if (gcd(i + 1, j + 1) == 1 && i != j) { dp[2][i][j] = 1; } } } for (int k = 3; k <= n; ++k) { for (int i = 0; i < 6; ++i) { for (int j = 0; j < 6; ++j) { if (gcd(i + 1, j + 1) == 1 && i != j) { for (int h = 0; h < 6; ++h) { if (gcd(h + 1, i + 1) == 1 && h != i && h != j) { dp[k][i][j] = (dp[k][i][j] + dp[k - 1][h][i]) % mod; } } } } } } int ans = 0; for (int i = 0; i < 6; ++i) { for (int j = 0; j < 6; ++j) { ans = (ans + dp[n][i][j]) % mod; } } return ans; }private int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }}class Solution {public: int distinctSequences(int n) { if (n == 1) return 6; int mod = 1e9 + 7; vector<vector<vector<int>>> dp(n + 1, vector<vector<int>>(6, vector<int>(6))); for (int i = 0; i < 6; ++i) for (int j = 0; j < 6; ++j) if (gcd(i + 1, j + 1) == 1 && i != j) dp[2][i][j] = 1; for (int k = 3; k <= n; ++k) for (int i = 0; i < 6; ++i) for (int j = 0; j < 6; ++j) if (gcd(i + 1, j + 1) == 1 && i != j) for (int h = 0; h < 6; ++h) if (gcd(h + 1, i + 1) == 1 && h != i && h != j) dp[k][i][j] = (dp[k][i][j] + dp[k - 1][h][i]) % mod; int ans = 0; for (int i = 0; i < 6; ++i) for (int j = 0; j < 6; ++j) ans = (ans + dp[n][i][j]) % mod; return ans; } int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }};class Solution: def distinctSequences(self, n: int) -> int: if n == 1: return 6 mod = 10 ** 9 + 7 dp = [[[0] * 6 for _ in range(6)] for _ in range(n + 1)] for i in range(6): for j in range(6): if gcd(i + 1, j + 1) == 1 and i != j: dp[2][i][j] = 1 for k in range(3, n + 1): for i in range(6): for j in range(6): if gcd(i + 1, j + 1) == 1 and i != j: for h in range(6): if gcd(h + 1, i + 1) == 1 and h != i and h != j: dp[k][i][j] += dp[k - 1][h][i] ans = 0 for i in range(6): for j in range(6): ans += dp[- 1][i][j] return ans % modVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.