题目
把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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof
解题思路
解题思路引用自 LeetCode 作者 huwt :点击这里访问题解原文
-
析题
- 一个骰子有6个面,6种结果 ( n=1 )
- n 个骰子有 6n-1 种点数和结果,点数和的大小范围为 n~6n-1 ( n>=2 )
- 题目的结果其实就是计算所有骰子点数和出现的次数,然后除以总的可能数
- 总的可能性容易求出,为 6 ^ n(6的n次方)
- 问题的关键在于求解每一个点数和出现的次数
-
动态规划解题
- 使用动态规划解决问题一般分为三步:
- 表示状态
- 找出状态转移方程
- 边界处理
- 使用动态规划解决问题一般分为三步:
逐步分析
1. 表示状态
-
分析问题的状态时,不要分析整体,只分析最后一个阶段即可!
- 因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案就是在最后一个阶段。
-
最后一个阶段是什么呢?
- 通过题目我们知道一共投掷 n 枚骰子,那最后一个阶段很显然就是:
- 当投掷完 n 枚骰子后,各个点数出现的次数(前 n 枚骰子的点数和)。
注意,这里的点数指的是前 n 枚骰子的点数和,而不是第 n 枚骰子的点数,下文同理。
-
找出了最后一个阶段,如何表示状态
- 首先用 变量 t = n 来表示需要掷多少枚骰子,每掷完一枚骰子 t - 1 。
- 定义一个 map
- 用 map 的 key 来表示投掷完这些骰子后,可能出现的点数和。
- map 的 value 就表示,该阶段各个点数出现的次数。
所以状态表示就是这样的:map [int] int ,表示投掷完 n 枚骰子后,点数 key 的出现次数 value 。
2. 找出状态转移方程
-
也就是找各个阶段之间的转化关系,同样只需分析最后一个阶段。
- 最后一个阶段 t = 0 也就是投掷完 n 枚骰子后的这个阶段
-
单单看一枚骰子,它的点数可能为 1, 2, 3, … 6
- 因此投掷完 n 枚骰子后点数和出现的次数,可以由投掷完 n - 1 枚骰子后,对应点数+第 n 枚骰子会出现的六个点数(1~6)的点数和,出现的次数之和
t := n
m := map[int]int{0:1}
for t > 0 {
m1 := map[int]int{}
for i := 1; i <= 6; i++ {
for k, v := range m {
m1[k + i] += v
}
}
m = m1
t--
}
t 表示阶段,k + i 表示投掷完 n 枚骰子后的点数和,i 表示第 n 枚骰子会出现的六个点数。
3. 边界处理
-
这里的边界处理很简单,只要我们把可以直接知道的状态初始化就好了。
-
我们可以直接知道的状态是啥,就是第一阶段的状态:
-
投掷完 1 枚骰子后,它的可能点数分别为 1, 2, 3, … , 6 并且每个点数出现的次数都是 1
m1 := map[int]int{}
for i := 1; i <= 6; i++ {
for k, v := range m {
m1[k + i] += v
}
}
m = m1
代码
–执行用时:0 ms --内存消耗:2.1 MB
func twoSum(n int) []float64 {
//表示阶段
t := n
//每个点数出现的次数都是 1
m := map[int]int{0:1}
for t > 0 {
//掷第 n 枚骰子时初始化
m1 := map[int]int{}
//边界处理
for i := 1; i <= 6; i++ {
for k, v := range m {
//k + i 表示投掷完 n 枚骰子后的点数和
// += v n-1 个骰子时对应的点数+第 n 枚骰子会出现的六个点数(1~6)的点数和的出现次数
m1[k + i] += v
}
}
// 保存 n-1 个骰子时对应点数的出现次数
m = m1
t--
}
//掷骰总次数
sum := 0
for _, v := range m {
sum += v
}
//sum := math.Pow(6.0,float64(n))
//计算每个点数和的概率
ret := make([]float64, n * 6 - n + 1)
for k, v := range m {
//数组下标刚好为 k - n
ret[k - n] = float64(v)/float64(sum)
}
return ret
}