Source:
- LintCode: (20) Dices Sum
Description
Throw n dices, the sum of the dices’ faces is S. Given n, find the all possible value of S along with its probability.
Example:
Example 1:
Input: n = 1
Output: [[1, 0.17], [2, 0.17], [3, 0.17], [4, 0.17], [5, 0.17], [6, 0.17]]
Explanation: Throw a dice, the sum of the numbers facing up may be 1, 2, 3, 4, 5, 6, and the probability of each result is 0.17.Example 2:
Input: n = 2
Output: [[2,0.03],[3,0.06],[4,0.08],[5,0.11],[6,0.14],[7,0.17],[8,0.14],[9,0.11],[10,0.08],[11,0.06],[12,0.03]]
Explanation: Throw two dices, the sum of the numbers facing up may be in [2,12], and the probability of each result is different.
Analysis & Solution
这道题要求掷n次骰子得到的和为各个值的概率。首先,和的范围是[n, 6n],返回的结果应当包含其中每个值和对应的概率的键值对。很自然的想法是,将和分解为每次扔骰子的点数,最后和为某个值的概率就是分法数乘上$(\frac{1}{6})^n$。然而,可想而知n一旦变大,分法数增长得可怕。
更重要的是,在操作的过程中,有重复的计算操作。例如,看扔4次和为6的条件下,其中的两种分法:
\begin{equation*}\begin{split}
6 &= 3(前两次点数之和) + 1(第三次点数) + 2(第四次点数)\\
&= 3(前两次点数之和) + 2(第三次点数) + 1(第四次点数)\\
\end{split}\end{equation*}
就这两种分法而言,不管后两次扔了多少,前两次扔的和为3的分法数都是要计算的,于是这个数就被计算了两次。随着n增大,这样重复的计算比比皆是,于是,一个自然的想法就是:自底而上,将前面的结果存起来(比如前两次扔到3的分法数之和),也就是动态规划。
没错,这道题也是典型的DP,鉴于分解数和概率只有一个$(\frac{1}{6})^n$的差别,这里就直接在矩阵中存储概率值了,代码如下:
1 | public List<Map.Entry<Integer, Double>> dicesSum(int n) { |
probs[i][j]
表示掷i
次骰子和为j
的概率,递推公式就是将最后一次掷骰子可能的点数遍历一遍,概率求和:
$$probs[i][j] = \sum_\limits{k=k_1}^{k_2}probs[i-1][j-k] \times \frac{1}{6}$$
k1
和k2
是最后一次掷骰子点数的取值范围,很容易算出。
另外,DP过程中可以发现,求掷i
次各个和的概率时,只要用到掷i-1
次各个和的概率,掷0 ~ i-2次的结果都用不上,而且纵坐标大于6i的部分都被浪费了。于是,从节省空间的角度,可以将矩阵简化为一个size = 6n
的数组,每一轮更新下标为[i, 6i]的部分,即掷i
次和为各个值的概率:
1 | public List<Map.Entry<Integer, Double>> dicesSum(int n) { |