剑指 Offer 60. n个骰子的点数
题目
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
示例 1:
输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
限制:
1 <= n <= 11
思路
这道题暴力会越界,可以使用动态规划。
给定 n个骰子,可得:
- 每个骰子摇到 1 至 6的概率相等,都为 1 / 6 1/6 1/6
- 将每个骰子的点数看作独立情况,共有
6
n
6^n
6n种「点数组合」。 例如n=2时的点数组合为:
( 1 , 1 ) , ( 1 , 2 ) , ⋯ , ( 2 , 1 ) , ( 2 , 2 ) , ⋯ , ( 6 , 1 ) , ⋯ , ( 6 , 6 ) ( 1 , 1 ) , ( 1 , 2 ) , ⋯ , ( 2 , 1 ) , ( 2 , 2 ) , ⋯ , ( 6 , 1 ) , ⋯ , ( 6 , 6 ) ( 1 , 1 ) , ( 1 , 2 ) , ⋯ , ( 2 , 1 ) , ( 2 , 2 ) , ⋯ , ( 6 , 1 ) , ⋯ , ( 6 , 6 ) (1,1),(1,2),⋯ ,(2,1),(2,2),⋯ ,(6,1),⋯ ,(6,6) (1,1),(1,2), \cdots, (2, 1), (2, 2), \cdots, (6,1), \cdots, (6, 6)(1,1),(1,2),⋯,(2,1),(2,2),⋯,(6,1),⋯,(6,6) (1,1),(1,2),⋯ ,(2,1),(2,2),⋯ ,(6,1),⋯ ,(6,6)(1,1),(1,2),⋯,(2,1),(2,2),⋯,(6,1),⋯,(6,6)(1,1),(1,2),⋯,(2,1),(2,2),⋯,(6,1),⋯,(6,6) n个骰子「点数和」的范围为 [ n , 6 n ] [n,6n] [n,6n] - 数量为 6 n − n + 1 = 5 n + 1 6n−n+1=5n+1 6n−n+1=5n+1种。
设输入 n个骰子的解(即概率列表)为 d p ( n ) dp(n) dp(n),其中「点数和」 x 的概率为 d p [ n ] [ x ] dp[n][x] dp[n][x] 。
假设已知 n−1个骰子的解
d
p
[
n
−
1
]
dp[n-1]
dp[n−1] ,此时添加一枚骰子,求 n 个骰子的点数和为 x的概率
d
p
[
n
]
[
x
]
dp[n][x]
dp[n][x] 。
当添加骰子的点数为 1 时,前 n−1个骰子的点数和应为 x−1 ,方可组成点数和 x ;
同理,当此骰子为 2 时,前 n−1个骰子应为 x−2;以此类推,直至此骰子点数为 6 。
将这 6种情况的概率相加,即可得到概率
d
p
[
n
]
[
x
]
dp[n][x]
dp[n][x] 。递推公式如下所示:
d p [ n ] [ x ] dp[n][x] dp[n][x]= ∑ i = 1 6 \sum_{i=1}^{6} ∑i=16 d p [ n − 1 ] [ x − i ] / 6 dp[n-1][x-i]/6 dp[n−1][x−i]/6
根据以上分析,得知通过子问题的解
d
p
[
n
−
1
]
dp[n-1]
dp[n−1] 可递推计算出
d
p
[
n
]
dp[n]
dp[n] ,而输入一个骰子的解
d
p
[
1
]
dp[1]
dp[1] 已知,因此可通过解
d
p
[
1
]
dp[1]
dp[1] 依次递推出任意解
d
p
[
n
]
dp[n]
dp[n] ,如下图所示,
n
=
2
,
x
=
7
n=2, x=7
n=2,x=7:
观察发现,以上递推公式虽然可行,但
d
p
[
n
−
1
]
dp[n-1]
dp[n−1] 中的
x
−
i
x−i
x−i会有越界问题。例如,若希望递推计算
d
p
[
2
]
[
2
dp[2][2
dp[2][2 ,由于一个骰子的点数和范围为
[
1
,
6
]
[1,6]
[1,6],因此只应求和
d
p
[
1
]
[
1
]
dp[1][1]
dp[1][1] ,即
d
p
[
1
]
[
0
]
dp[1][0]
dp[1][0],
d
p
[
1
]
[
−
1
]
dp[1][-1]
dp[1][−1],
d
p
[
1
]
[
−
2
]
dp[1][-2]
dp[1][−2] ,
d
p
[
1
]
[
−
3
]
dp[1][-3]
dp[1][−3],
d
p
[
1
]
[
−
4
]
dp[1][-4]
dp[1][−4] 皆无意义。因此,计算的时候需要添加判断保证
x
−
i
x-i
x−i有效,即
x
−
i
>
0
x-i>0
x−i>0。
代码
class Solution {
public:
vector<double> dicesProbability(int n) {
// 二维dp
// dp[i][j] 表示i个骰子时总和为j的概率
vector<double> res(5*n+1);
vector<vector<double>> dp(n+1, vector<double>(6*n+1));
// 初始化
for(int i = 1; i <= 6; ++i)
dp[1][i] = 1.0/6;
// 顺序遍历
for(int i = 2; i <= n; ++i)
for(int j = i; j <= 6*i; ++j){
for(int k = 1; k <= 6; ++k){
// 当前选k,前面的影响
if(j - k > 0)
dp[i][j] += dp[i-1][j - k]/6;
else
break;
}
}
for(int i = n; i <= 6*n; ++i)
res[i - n] = dp[n][i];
return res;
}
};