不同骰子序列的数目
难度:
标签:
题目描述
代码结果
运行时间: 99 ms, 内存: 18.6 MB
/*
题目思路:
1. 使用动态规划来解决这个问题,并在可能的地方使用Java Stream进行简化。
2. 定义 dp[i][j] 表示长度为 i,最后一个数字是 j 的序列的数目。
3. 我们需要确保 j 与 dp[i-1] 的最后一个数字的最大公约数为 1。
4. 我们还需要确保在 i-3 之前没有相同的 j。
5. 最后计算所有 dp[n][j] 的和。
*/
import java.util.Arrays;
import java.util.stream.IntStream;
public class DiceSequencesStream {
private static final int MOD = 1000000007;
public int countSequences(int n) {
long[][] dp = new long[n + 1][7];
IntStream.rangeClosed(1, 6).forEach(j -> dp[1][j] = 1);
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= 6; j++) {
dp[i][j] = 0;
dp[i][j] = IntStream.rangeClosed(1, 6)
.filter(k -> gcd(j, k) == 1)
.mapToLong(k -> dp[i - 1][k])
.reduce(0, (a, b) -> (a + b) % MOD);
}
if (i > 2) {
for (int j = 1; j <= 6; j++) {
dp[i][j] = IntStream.rangeClosed(1, 6)
.filter(k -> j != k)
.mapToLong(k -> dp[i][j] - dp[i - 2][k] + MOD)
.reduce(dp[i][j], (a, b) -> (a + b) % MOD);
}
}
}
return IntStream.rangeClosed(1, 6)
.mapToLong(j -> dp[n][j])
.reduce(0, (a, b) -> (a + b) % MOD)
.intValue();
}
private int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public static void main(String[] args) {
DiceSequencesStream ds = new DiceSequencesStream();
System.out.println(ds.countSequences(4)); // 输出184
System.out.println(ds.countSequences(2)); // 输出22
}
}
解释
方法:
这个问题可以通过动态规划来解决。定义一个动态规划数组 f[i][j],其中 i 表示掷骰子的次数,j 表示最后一次掷的骰子结果为 j+1(因为j是从0开始的索引)。f[i][j] 的值是所有有效序列的数量,这些序列的最后一个数字是 j+1。初始化第一次掷骰子的结果时,每个结果都只有一种可能,所以 f[1][j] = 1。对于后续的每次掷骰,需要计算基于前一次结果的有效序列数量,同时考虑两个限制条件:1) 序列中任意相邻数字的最大公约数为 1;2) 序列中相等的值之间至少有2个其他值。为了实现这一点,我们对于每个可能的 j,将所有可能的 k(不等于 j 且 gcd(k+1, j+1) == 1)的 f[i-1][k] 加总,然后从中减去不符合第二个条件的序列数量。最后,将结果模 109 + 7。
时间复杂度:
O(n)
空间复杂度:
O(n)
代码细节讲解
🦆
在定义动态规划数组 f[i][j] 时,为什么选择 j 来表示最后一次掷的骰子结果为 j+1,而不是直接使用骰子的结果作为索引?
▷🦆
动态规划的转移方程中,如何确保减去的不符合条件的序列数量是正确的?具体是如何处理的?
▷🦆
对于骰子的结果相等的限制条件(至少间隔2个其他值),为什么在计算 f[i][j] 时只减去了 f[i-2][j] 而不考虑更长的间隔?
▷🦆
在考虑骰子结果的最大公约数为1的条件时,gcd函数是如何在代码中实现的?是否有现成的函数或需要自行实现?
▷