题目
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]
思路
动态规划求解。
当有n个骰子时,点数和的最小值为n,最大值为n * 6。
创建一个n行6n列的二维数组,dp [ i ] [ j ] 表示 i 个骰子、点数和为 j 时出现的次数。
当只有一个骰子时,点数和可能出现的情况有1,2,3,4,5,6,并且每种出现的次数都是1。
当增加一个骰子时,这个骰子出现的点数也是从1到6,并且出现的概率都相同。当点数和为s时,新的骰子出现1,则旧的骰子点数为s-1,新的骰子点数为2,则旧的骰子点数为s-2,…。所以此时点数和s出现的次数就是(s-1)、(s-2)、(s-3)、(s-4)、(s-5)、(s-6)出现的次数之和。
两个骰子出现的点数和s的范围是从n=2到n*6=18。
代码
class Solution:
def twoSum(self, n: int) -> List[float]:
dp = [[0 for _ in range(6*n+1)] for _ in range(n+1)]
for j in range(1,7):
dp[1][j]=1
for i in range(2,n+1):
for j in range(i,6*i+1):
for s in range(1,7):
if j-s<0: break
dp[i][j]+=dp[i-1][j-s]
res=[]
for i in range(n,6*n+1):
res.append(dp[n][i]/(6**n))
return res
优化
上面的算法的空间复杂度为O(n*n),但是会有很多空间并没有用到或者用过一次就不再用了。因此想办法从空间复杂度上进行优化。
我们发现第n个骰子的数据是通过前n-1个骰子保存的数据求解的。因此每次求解只需要知道上一次的结果即可。我们申请一个一维数组,每增加一个骰子时,就在数组里从右往左更新数值。注意每轮更新时,需要先把数组中第一次要填的位置初始化为0。
代码
class Solution:
def twoSum(self, n: int) -> List[float]:
dp = [0 for _ in range(6*n+1)]
for j in range(1,7):
dp[j]=1
for i in range(2,n+1):
for j in range(6*i,i-1,-1):
dp[j]=0
for s in range(1,7):
if j-s<i-1: break
dp[j]+=dp[j-s]
res=[]
for i in range(n,6*n+1):
res.append(dp[i]/(6**n))
return res
复杂度
优化之前:
时间复杂度:O(n^2)
空间复杂度:O((n+1)(6n+1))=O(n^2)
优化之后:
时间复杂度:O(n^2)
空间复杂度:O(n)