《剑指offer》刷题系列——(六十二)n个骰子的点数

题目

把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)

猜你喜欢

转载自blog.csdn.net/weixin_44776894/article/details/107509128