一文解决投骰子类的算法题

目录

首先来看一道经典的问题:n个骰子的点数

我们再来看另一个问题:掷骰子的N种方法


牢记投骰子类问题的解决方法:动态规划

首先来看一道经典的问题:n个骰子的点数

题目是这样的:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。

示例 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]

预备知识

给定 n 个骰子,可得:

  • 每个骰子摇到 1 至 6 的概率相等,都为 \frac{1}{6} 。

  • 将每个骰子的点数看作独立情况,共有 6^{n} 种「点数组合」。例如 n = 2 时的点数组合为:

(1,1), (1,2), ... , (2, 1), (2, 2), ... , (6,1), ... , (6, 6)

  • n 个骰子「点数和」的范围为 [n, 6n],数量为 5n+1 种。

        设输入 n 个骰子的解(即概率列表)为 f(n) ,其中「点数和」 x 的概率为 f(n, x)。 

        当添加骰子的点数为 1 时,前 n - 1 个骰子的点数和应为 x - 1,方可组成点数和 x ;同理,当此骰子为 2 时,前 n - 1 个骰子应为 x - 2 ;以此类推,直至此骰子点数为 6 。将这 6 种情况的概率相加,即可得到概率 f(n, x)。递推公式如下所示:

f(n,x)=\sum_{i=1}^{6}f(n-1,x-i)*\frac{1}{6}

        根据以上分析,得知通过子问题的解 f(n - 1)可递推计算出 f(n),而输入一个骰子的解 f(1) 已知,因此可通过解 f(1) 依次递推出任意解 f(n) 。         

Picture2.png

        观察发现,以上递推公式虽然可行,但 f(n - 1, x - i) 中的 x - i 会有越界问题。例如,若希望递推计算 f(2, 2) ,由于一个骰子的点数和范围为 [1, 6] ,因此只应求和 f(1, 1) ,即 f(1, 0) , f(1, -1) , ... , f(1, -4) 皆无意义。此越界问题导致代码编写的难度提升。

        所以为了计算 f(n, x) ,将所有与之有关的情况求和;而倘若改换为 “正向” 的递推公式,便可解决越界问题。

逆向递推(有越界问题):为求f(n,x),将f(n-1)中所有与其有关的概率项求和。

正向递推(无越界问题):遍历f(n-1),统计每项f(n-1,i)对概率f(n,i+1),f(n,i+2),...,f(n,i+6)产生的贡献。

        具体来看,由于新增骰子的点数只可能为 1 至 6 ,因此概率 f(n - 1, x) 仅与 f(n, x + 1) , f(n, x + 2), ... , f(n, x + 6) 相关。因而,遍历 f(n - 1) 中各点数和的概率,并将其相加至 f(n) 中所有相关项,即可完成 f(n - 1) 至 f(n) 的递推。 

        通常做法是声明一个二维数组 dp ,dp[i][j] 代表前 i 个骰子的点数和 j 的概率,并执行状态转移。而由于 dp[i] 仅由 dp[i-1] 递推得出,为降低空间复杂度,只建立两个一维组 dp , tmp 交替前进即可。


我们再来看另一个问题:掷骰子的N种方法

题目:这里有 d 个一样的骰子,每个骰子上都有 f 个面,分别标号为 1, 2, ..., f

我们约定:掷骰子的得到总点数为各骰子面朝上的数字的总和。

如果需要掷出的总点数为 target,请你计算出有多少种不同的组合情况(所有的组合情况总共有 f^d 种),模 10^9 + 7 后返回。

示例 1:

输入:d = 1, f = 6, target = 3
输出:1

示例 2:

输入:d = 2, f = 6, target = 7
输出:6

示例 3:

输入:d = 2, f = 5, target = 10
输出:1

        这道题的处理方法其实和上一道题一样,只是做了稍微的变通。

        首先我们定义 dp[i][j] 代表投掷 i 个骰子和为 j。而dp[i][j] 和 dp[i-1] 的关系是,当第 i 次我投的点数为k(1<= k <= f),那么前 i-1次和为 j-k,对应的是 dp[i-1][j-k]。

        那么最终就有 dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] + ... + dp[i-1][j-f];

猜你喜欢

转载自blog.csdn.net/ThinPikachu/article/details/122565752