LeetCode 剑指 Offer 60. n个骰子的点数
题目描述

题意分析
将
n个公平骰子各掷一次,返回点数和从n到6n的概率。等概率的是每个骰子点数确定的投掷序列,不是不同的点数和,因此先统计每个和对应多少种序列,再除以总序列数 $6^n$。
解法:滚动动态规划统计方案数
核心思路
[!blue]
处理完
i个骰子后,dp[sum]表示这i个骰子点数和为sum的投掷序列数。后续只关心已有的点数和,不关心此前各个骰子的具体点数,所以这些序列可以合并计数。加入第
i + 1个骰子时,它可能取1..6中的任一点数point。每个旧序列都能接上这个点数,得到和为sum + point的新序列,因此执行next[sum + point] += dp[sum]。反过来,任意新序列去掉最后一个骰子,就唯一对应一个旧序列和一个point,所以转移不重不漏。一个骰子的六个点数各有一种序列,作为初始状态。每轮创建全零的
next,只读取dp,完成这一轮后再替换;同时把总序列数乘 6。最终将每个计数除以总数即可。
解题步骤
- 令
dp[1..6] = 1,total = 6,表示已经处理一个骰子。- 加入第
dice个骰子时,上一轮合法的和为dice - 1到6 * (dice - 1)。- 对每个旧点数和枚举新骰子的六种点数,将计数累加到
next[sum + point]。- 用
next替换dp,并执行total *= 6,直到处理完n个骰子。- 从小到大枚举
sum = n..6n,将概率写入ans[sum - n]。当n = 1时没有转移,直接由初始状态得到答案。
代码实现
class Solution {
public double[] dicesProbability(int n) {
long[] dp = new long[6 * n + 1];
for (int point = 1; point <= 6; point++) {
dp[point] = 1;
}
long total = 6;
for (int dice = 2; dice <= n; dice++) {
// 新一轮从零开始,只读取上一轮,避免同一骰子重复参与。
long[] next = new long[6 * n + 1];
for (int sum = dice - 1; sum <= 6 * (dice - 1); sum++) {
for (int point = 1; point <= 6; point++) {
next[sum + point] += dp[sum];
}
}
dp = next;
total *= 6;
}
double[] ans = new double[5 * n + 1];
for (int sum = n; sum <= 6 * n; sum++) {
// 点数和从 n 开始,写入下标要减去偏移,并先转浮点数。
ans[sum - n] = (double) dp[sum] / total;
}
return ans;
}
}
func dicesProbability(n int) []float64 {
dp := make([]int64, 6*n+1)
for point := 1; point <= 6; point++ {
dp[point] = 1
}
total := int64(6)
for dice := 2; dice <= n; dice++ {
// 新一轮从零开始,只读取上一轮,避免同一骰子重复参与。
next := make([]int64, 6*n+1)
for sum := dice - 1; sum <= 6*(dice-1); sum++ {
for point := 1; point <= 6; point++ {
next[sum+point] += dp[sum]
}
}
dp = next
total *= 6
}
ans := make([]float64, 5*n+1)
for sum := n; sum <= 6*n; sum++ {
// 点数和从 n 开始,写入下标要减去偏移,并先转浮点数。
ans[sum-n] = float64(dp[sum]) / float64(total)
}
return ans
}
复杂度分析
- 时间复杂度:$O(n^2)$。加入第
k个骰子时,枚举 $5(k-1)+1$ 个旧点数和,每个状态固定转移 6 次;各轮长度累加为平方级。- 空间复杂度:$O(n)$。
dp、next和答案数组的长度都与n成正比,只保留相邻两轮状态。
关键点总结
[!green]
dp保存序列数量,概率在最后统一计算。- 按最后一个骰子的点数分类,使所有投掷序列恰好被统计一次。
- 第
i轮只存在i..6i这些点数和,范围外的状态始终为零。- 点数和从
n开始,答案下标则从零开始,两者相差n。
易错点总结
[!yellow]
- 答案长度是
6n - n + 1 = 5n + 1,不是6n + 1。- 不能在同一数组上正向累加,否则本轮新产生的状态会继续转移,重复使用同一个骰子。
total初始为 6,之后恰好乘n - 1次 6,最终才是 $6^n$。- 除法前必须转换为浮点数,否则整数除法会丢失概率的小数部分。本题
n最大为 11,使用long/int64足以保存计数和总序列数。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 1155. 掷骰子等于目标和的方法数 | 中等 | 先统计各总点数的方案数,再除以6的n次方得到概率;原题只问给定目标点数的计数。 |
| 1230. 抛掷硬币 | 中等 | 同样逐次卷积概率状态,本题每个骰子有六个等概率结果,硬币只有两个且概率可不同。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!