剑指 Offer 60. n个骰子的点数(动态规划)

文章目录

  • 题目描述
  • 思路分析
  • 完整代码

题目描述

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

思路分析

我读完这道题愣了一会。反应了半天才看懂题。

简单说一下题目,扔1次骰子,则可能的点数,1,2 ,3,4, 5,6。每一种出现的概率就是 1/6。所以当扔骰子数n=1时,应该返回[1/6,1/6,1/6,1/6,1/6,1/6]

当n=2的时候,看下面的表。
剑指 Offer 60. n个骰子的点数(动态规划)_第1张图片
其实就是统计出投骰子的总和点数,计算出这些点数的出现概率:
P (k) =k出现的次数/总次数

总次数好说,因为一共有 n 枚骰子,每枚骰子的点数都有 6 种可能出现的情况。
1次骰子是 6 ,2次骰子是6 * 6。n次就是 6的n次方。

那么现在就是求k出现的总次数了。这里的k可以理解为一个列表,这个列表里存储了n个骰子出现所有数字的和的次数。

使用动态规划来解决,找出这个列表k。

看子问题的递进过程。

当只有一个骰子n=1时候,毫无疑问,总和可能性为[1,2,3,4,5,6]
当n=2的时候,总和可能性为[2,3,4,5,6,7,8,9,10,11,12]

拿其中的总和=3举例:两个骰子出现3,意味着:

  • 第一次投1,第二次投2
  • 或者第一次投2,第二次投1。

和上一轮的投掷结果相关,显然递推关系就基本出来了。

定义dp[i][j] 表示第i次投,总和为j时,有多少种组合数。

递推公式:dp[i][j] = dp[i-1][j-1]+ dp[i-1][j-2]+ dp[i-1][j-3]… dp[i-1][j-6]

这个式子其实就是先找到上一个状态,就是不加本次骰子的所有点数和,看看那些能凑成本次需要的j,将这些可能性全部相加即可。


        for i in range(2,n+1): # 第i个骰子
            for j in range(i,6*i+1): #j表示点数和,且最大为6*i
                for k in range(1,7): # 相加所有可以组成j的情况
                    if j < k:break  # 显然在递推公式里j>k,做边界处理
                    dp[i][j] +=dp[i-1][j-k]

初始化:
将n=1的情况初始化就行了。
为了方便理解,数组下标为0时的情况作废,数组下标为1就代表n=1 就是第一次投掷的情况。

完整代码

class Solution:
    def dicesProbability(self, n: int) -> List[float]:
        # dp[i][j] 表示投掷第i次,点数和为j,的组合次数有多少个
        num_sum = 6 ** n

        dp = [[0 for _ in range(6*n+1)] for _ in range(n+1)]

        for p in range(1,7):
            dp[1][p] = 1

        for i in range(2,n+1): # 第i个骰子
            for j in range(i,6*i+1): #j表示点数最大为6*i
                for k in range(1,7):
                    if j < k:break
                    dp[i][j] +=dp[i-1][j-k]

        res = []
        for i in range(n,n*6+1):
            res.append(dp[n][i]/num_sum)
        return res
    ```

你可能感兴趣的:(动态规划,算法)