题目描述
有一个骰子模拟器会每次投掷的时候生成一个 1 到 6 的随机数。
不过我们在使用它时有个约束,就是使得投掷骰子时,连续 掷出数字 i 的次数不能超过 rollMax[i](i 从 1 开始编号)。
现在,给你一个整数数组 rollMax 和一个整数 n,请你来计算掷 n 次骰子可得到的不同点数序列的数量。
假如两个序列中至少存在一个元素不同,就认为这两个序列是不同的。由于答案可能很大,所以请返回 模 10^9 + 7 之后的结果。
示例 1:
输入:n = 2, rollMax = [1,1,2,2,2,3]
输出:34
解释:我们掷 2 次骰子,如果没有约束的话,共有 6 * 6 = 36 种可能的组合。但是根据 rollMax 数组,数字 1 和 2 最多连续出现一次,所以不会出现序列 (1,1) 和 (2,2)。因此,最终答案是 36-2 = 34。
示例 2:
输入:n = 2, rollMax = [1,1,1,1,1,1]
输出:30
示例 3:
输入:n = 3, rollMax = [1,1,1,2,2,3]
输出:181
提示:
- 1 <= n <= 5000
- rollMax.length == 6
- 1 <= rollMax[i] <= 15
思路
一开始觉得是个数学题,受到其他题解的启发,才想起来可以使用动态规划。
定义状态
dp[i][j] 表示i+1次掷骰子后,最后1次为j的合法序列数量。i和j都从0开始,所以i的取值范围是[0,n),j的取值范围是[0, 5]。
状态转移方程
i < rollMax[j]dp[i][j] = sum(dp[i-1])i = rollMax[j]dp[i][j] = sum(dp[i-1]) - 1i > rollMax[j]dp[i][j] = sum(dp[i-1]) - (sum(dp[i-rollMax[j]-1]) - dp[i-rollMax[j]-1][j])
怎么理解上述的状态转移方程呢?
如果没有任何限制,那么显然,dp[i][j] = sum(dp[i-1]),我们要处理的就是要减去超出限制的情况。
当i < rollMax[j],不可能超出限制,所以不用考虑减;当i = rollMax[j],那么唯一的超出限制的情况就是从第0次到第i次,全部结果是j的情况,所以只要减1即可。最复杂的是i > rollMax[j]的情况,这种情况下,超出限制的可能就是在第i次之前,前面已经连续出现了rollMax[j]次j了,这种情况就是sum(dp[i-rollMax[j]-1]) - dp[i-rollMax[j]-1][j],这里减去dp[i-rollMax[j]-1][j]是因为第[i-rollMax[j]-1]次不能出现j,否则第i-1次就超出限制了。
Java版本代码
class Solution {
private static final long MOD = 1000000007L;
public int dieSimulator(int n, int[] rollMax) {
long[][] dp = new long[n][6];
for (int j = 0; j < 6; j++) {
dp[0][j] = 1;
}
for (int i = 1; i < n; i++) {
for (int j = 0; j < 6; j++) {
dp[i][j] = Arrays.stream(dp[i-1]).sum();
if (i > rollMax[j]) {
dp[i][j] = (dp[i][j] - (Arrays.stream(dp[i-rollMax[j]-1]).sum() - dp[i-rollMax[j]-1][j])) % MOD;
while (dp[i][j] < 0) {
dp[i][j] += MOD;
}
} else if (i == rollMax[j]) {
// dp[i][j] = (dp[i][j] - dp[i-rollMax[j]][j] + MOD) % MOD;
dp[i][j] = (dp[i][j] - 1) % MOD;
}
// dp[i][j] %= MOD;
}
}
return (int) (Arrays.stream(dp[n-1]).sum() % MOD);
}
}
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情