剑指 Offer 60. n个骰子的点数

该文讲述了如何解决计算多个骰子投掷后点数和的概率问题,采用动态规划方法,通过递推公式计算每个点数和出现的概率,处理了边界条件以避免越界。最终将结果存储在浮点数数组中返回。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

剑指 Offer 60. n个骰子的点数

题目

把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

思路

这道题暴力会越界,可以使用动态规划
给定 n个骰子,可得:

  • 每个骰子摇到 1 至 6的概率相等,都为 1 / 6 1/6 1/6
  • 将每个骰子的点数看作独立情况,共有 6 n 6^n 6n种「点数组合」。 例如n=2时的点数组合为:
    ( 1 , 1 ) , ( 1 , 2 ) , ⋯   , ( 2 , 1 ) , ( 2 , 2 ) , ⋯   , ( 6 , 1 ) , ⋯   , ( 6 , 6 ) ( 1 , 1 ) , ( 1 , 2 ) , ⋯   , ( 2 , 1 ) , ( 2 , 2 ) , ⋯   , ( 6 , 1 ) , ⋯   , ( 6 , 6 ) ( 1 , 1 ) , ( 1 , 2 ) , ⋯ , ( 2 , 1 ) , ( 2 , 2 ) , ⋯ , ( 6 , 1 ) , ⋯ , ( 6 , 6 ) (1,1),(1,2),⋯ ,(2,1),(2,2),⋯ ,(6,1),⋯ ,(6,6) (1,1),(1,2), \cdots, (2, 1), (2, 2), \cdots, (6,1), \cdots, (6, 6)(1,1),(1,2),⋯,(2,1),(2,2),⋯,(6,1),⋯,(6,6) (1,1),(1,2),,(2,1),(2,2),,(6,1),,(6,6)(1,1),(1,2),,(2,1),(2,2),,(6,1),,(6,6)(1,1),(1,2),,(2,1),(2,2),,(6,1),,(6,6) n个骰子「点数和」的范围为 [ n , 6 n ] [n,6n] [n,6n]
  • 数量为 6 n − n + 1 = 5 n + 1 6n−n+1=5n+1 6nn+1=5n+1种。

设输入 n个骰子的解(即概率列表)为 d p ( n ) dp(n) dp(n),其中「点数和」 x 的概率为 d p [ n ] [ x ] dp[n][x] dp[n][x]

假设已知 n−1个骰子的解 d p [ n − 1 ] dp[n-1] dp[n1] ,此时添加一枚骰子,求 n 个骰子的点数和为 x的概率 d p [ n ] [ x ] dp[n][x] dp[n][x]
当添加骰子的点数为 1 时,前 n−1个骰子的点数和应为 x−1 ,方可组成点数和 x ;
同理,当此骰子为 2 时,前 n−1个骰子应为 x−2;以此类推,直至此骰子点数为 6 。
将这 6种情况的概率相加,即可得到概率 d p [ n ] [ x ] dp[n][x] dp[n][x] 。递推公式如下所示:

d p [ n ] [ x ] dp[n][x] dp[n][x]= ∑ i = 1 6 \sum_{i=1}^{6} i=16 d p [ n − 1 ] [ x − i ] / 6 dp[n-1][x-i]/6 dp[n1][xi]/6

根据以上分析,得知通过子问题的解 d p [ n − 1 ] dp[n-1] dp[n1] 可递推计算出 d p [ n ] dp[n] dp[n] ,而输入一个骰子的解 d p [ 1 ] dp[1] dp[1] 已知,因此可通过解 d p [ 1 ] dp[1] dp[1] 依次递推出任意解 d p [ n ] dp[n] dp[n] ,如下图所示, n = 2 , x = 7 n=2, x=7 n=2,x=7
在这里插入图片描述
观察发现,以上递推公式虽然可行,但 d p [ n − 1 ] dp[n-1] dp[n1] 中的 x − i x−i xi会有越界问题。例如,若希望递推计算 d p [ 2 ] [ 2 dp[2][2 dp[2][2 ,由于一个骰子的点数和范围为 [ 1 , 6 ] [1,6] [1,6],因此只应求和 d p [ 1 ] [ 1 ] dp[1][1] dp[1][1] ,即 d p [ 1 ] [ 0 ] dp[1][0] dp[1][0], d p [ 1 ] [ − 1 ] dp[1][-1] dp[1][1], d p [ 1 ] [ − 2 ] dp[1][-2] dp[1][2] , d p [ 1 ] [ − 3 ] dp[1][-3] dp[1][3], d p [ 1 ] [ − 4 ] dp[1][-4] dp[1][4] 皆无意义。因此,计算的时候需要添加判断保证 x − i x-i xi有效,即 x − i > 0 x-i>0 xi>0

代码

class Solution {
public:
    vector<double> dicesProbability(int n) {
        // 二维dp
        // dp[i][j] 表示i个骰子时总和为j的概率
        vector<double> res(5*n+1);
        vector<vector<double>> dp(n+1, vector<double>(6*n+1));

        // 初始化
        for(int i = 1; i <= 6; ++i)
            dp[1][i] = 1.0/6;
        
        // 顺序遍历
        for(int i = 2; i <= n; ++i)
            for(int j = i; j <= 6*i; ++j){
                for(int k = 1; k <= 6; ++k){
                    // 当前选k,前面的影响
                    if(j - k > 0)
                        dp[i][j] += dp[i-1][j - k]/6;
                    else
                        break;
                }
            }
        
        for(int i = n; i <= 6*n; ++i)
            res[i - n] = dp[n][i];
        
        return res;

    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值