leetcode
leetcode 2001 ~ 2050
不同骰子序列的数目

不同骰子序列的数目

难度:

标签:

题目描述

代码结果

运行时间: 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,而不是直接使用骰子的结果作为索引?
在动态规划中,使用 j 来表示最后一次掷的骰子结果为 j+1 主要是为了简化数组索引的处理。由于数组索引通常从 0 开始,而骰子的结果范围是 1 到 6,直接使用 j+1 可以方便地将骰子的实际结果映射到从 0 开始的索引上。这种做法既可以避免在数组操作时进行额外的加减运算,也可以使代码更加直观易懂。
🦆
动态规划的转移方程中,如何确保减去的不符合条件的序列数量是正确的?具体是如何处理的?
在动态规划的转移方程中,确保减去的不符合条件的序列数量正确是通过精确计算特定条件下的序列数来实现的。在题解中,我们需要减去的是那些不满足第二个条件的序列,即相等的值之间至少有2个其他值的限制。这通过从总和中减去特定的 f[i-2][j] 来实现,这代表了在当前位置使用相同值且之间只隔了一个其他值的所有序列。通过这种方式,我们能精确控制并减去那些不满足条件的序列,从而保证最终的序列数符合题目要求。
🦆
对于骰子的结果相等的限制条件(至少间隔2个其他值),为什么在计算 f[i][j] 时只减去了 f[i-2][j] 而不考虑更长的间隔?
在计算 f[i][j] 时,只减去 f[i-2][j] 是基于题目条件的精确解释。题目要求相等的值之间至少有2个其他值的间隔,这意味着如果当前是第 i 次掷骰子并且结果与第 i-3 次的结果相同,则中间至少间隔了 i-2 和 i-1 两次结果,满足条件。因此,我们需要减去的只是这种最近的不满足条件的情况,即 i-2 次结果也是相同的情况,这就是为什么只考虑 f[i-2][j]。更长的间隔自然满足条件,不需要额外处理。
🦆
在考虑骰子结果的最大公约数为1的条件时,gcd函数是如何在代码中实现的?是否有现成的函数或需要自行实现?
在代码中实现最大公约数(gcd)的条件通常可以利用许多编程语言内置的 gcd 函数。例如,在 Python 中,可以直接使用 math 模块的 gcd 函数,如 `from math import gcd`。这个函数可以高效地计算两个整数的最大公约数。如果在某些环境或语言中没有现成的 gcd 函数,也可以使用较为简单的欧几里得算法手动实现。

相关问题