把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
动态规划:把种数存进动态规划的框里。当掷一次骰子时,总的情况有6种1,2,3,4,5,6.每种的概率都是1/6
掷两次骰子时,可以观察出总的点数范围为2--12.同理n次的范围为n-6n。因为最少为每个骰子都1,最多为每个骰子都n。
两次时总的情况为36.因为第一次有6种可能,第二次也是,所以总的种数可能是6的n次方。
那么2--12这些总的点数分别都有几种可能呢。比如2,只能是第一次1,第二次也1这一种可能。3可能是第一次1,第二次2,也可能是第一次2,第一次1,共两种。4可能是22,13,31三种。5可能是14,41,23,32四种以此类推。
抽象出来这些可能的种数怎么得到呢。只考虑最后一次n的情况,用dp[n,s]表示掷n次点数之和为s的种数。n由n-1的情况再掷一次得到,掷第n次有1,2,3,4,5,6这些可能,第n次是1时,前n-1次总和是s-1,第n次是2时,前n-1次总和是s-2,第n次是3时,前n-1次总和是s-3以此类推。因此dp[n,s]=dp[n-1,s-1]+dp[n-1,s-2]+dp[n-1,s-3]+dp[n-1,s-4]+dp[n-1,s-5]+dp[n-1,s-6],这就是状态转移方程。类比于跳台阶那道题,可以跳一阶或两阶。第n阶总的种类就由n-1和n-2的和得到。
初始化掷1次都为1,从1开始把0的位置放0.[0][1][1][1][1][1][1][1]
掷两次的结果由一次的得到。dp[2][2]=dp[1][1]。为了略去一些不符合的。比如2-6,2-5....2-2。用i-j>=0。i是点数总和,j是第n次掷的点数。因为第n次不能大于等于总的点数,从第二次开始,第一次至少有1,所以总的点数-第n次的点数>=1。dp[2][3]=dp[1][1]+dp[1][2],也就是第一次是1的种数第二次固定是2+第一次是2的种数第二次固定是1.
这样求出第n行的动态规划值以后用每个值除以6的n次方就是概率列表。
由于每次至于要第n-1行得到第n行的动态规划数组,为了节省空间,只用一行n列的数组来存储,但需要从后往前更新,因为如果从前往后,前面的数本来是n-1行的种数更新成n行的了,后面的就不对了。同时在更新自身的时候应该先把自身为0 dp[i]=0.因为 dp[i]+=dp[i-j]这句如果自身原本有值会造成干扰。
class Solution:
def twoSum(self, n: int) -> List[float]:
#动态规划
if n==0:
return []
dp = [0]*(6*n+1)
for i in range(1,7):
dp[i]=1
for _ in range(n-1):#如n=1不更新,n=2更新一轮以此类推
for i in range(6*n,0,-1): #6n到1
dp[i]=0
for j in range(1,7):#1到6
if i-j>=1:
dp[i]+=dp[i-j]
res = []
all = 6**n
for i in range(n,6*n+1):#n到6n
res.append(dp[i]/all)
return res