剑指offer43-计算n个色子掷出各个点数和的概率

问题描述:把n个色子掷地上,所有朝上一面的和为S,求出所有可能的S的概率,分析可知,色子有6个面,其数是1~6,而总共有n个色子,那么其和的范围是n~6n,而总共可能出现的组合情况有6^n种。

/**************方法一**************/
通过每次确定一个色子的数字,确定n个色子出现的数字,然后根据其和,相应的次数+1,从第一个色子开始确定,逐个确定色子的数字,一个色子确定后,要确定下一个色子,这一过程通过递归来进行。而对于某个色子的数字确定,则是通过循环来进行,循环确定一个色子可能出现的几种情况。

#include <iostream>
using namespace std;
const int Biggest_Value = 6;
void ConfirmDicesNums(int* times, int CurDices, int CurSums, int n);
void PrintProbability_Solution1(int n)
{
    if (n == 0)
    {
        return;
    }
    int NumScale = Biggest_Value*n - n + 1;
    //定义用来存储各个和出现次数的数组
    int* Probability = new int[NumScale];
    memset(Probability, 0, NumScale*sizeof(int));
    //第一个色子可能出现的数字开始进入递归
    for (int i = 1; i <= Biggest_Value; i++)   
    {
    // 第一个色子编号为1,进入后为下一个色子,编号+1,此时的和
    //为第一个色子点数即i 
        ConfirmDicesNums(Probability, 1 + 1, i, n);   
    }
    for (int i = 0; i < NumScale;i++)
    {
        cout << i + n << ":" << Probability[i] / pow(Biggest_Value, n) << endl;
    }
}

CurDices代表当前色子的编号,而CurSums则代表的是之前确定下的色子的和,当前色子确定的数字为i,加上后为当前确定色子总和,在最后一个色子时候,进入下一个递归,那么CurDices+1则是超出了编号,而CurSums+i为加上了最后一个色子的数字后的结果,此时判断色子是否超出编号,然后对应的和出现次数+1;

void ConfirmDicesNums(int* times, int CurDices, int CurSums, int n)
{
   //如果色子编号到n+1,意味着已经确定所有色子的点数
    if (CurDices > n)        
    {
        times[CurSums - n]++;
        return;
    }
    //本色子可能情况也有1~Biggest_Value种
    //每确定一次,则进入递归确定下一个
    for (int i = 1; i <= Biggest_Value; i++)           
    {
        ConfirmDicesNums(times, CurDices + 1, CurSums + i, n);
    }
}

/*************************************/

/******************方法二********************/
本方法是通过色子掷出的数字规律来计算每个和的次数:
当只有一个色子的时候,数字范围是1~6,两个的时候为2~12,…,n个色子的时候为n~6n。
对于第k个色子而言,其可能掷出的情况有6种,即1~6。假设掷出第k个色子后,其和为sum,如果要使得k各色子的和为sum的话:
当第k个色子掷出1的时候,前面k-1个色子应该和为sum-1;
当第k个色子掷出2的时候,前面k-1个色子应该和为sum-2;
以此类推当掷出6,则前面k-1个色子,应该掷出sum-6;
也就是说,对于k个色子而言,和为sum的情况有6种,即当k-1个色子和分别为sum-1、sum-2、sum-3、sum-4、sum-5、sum-6的时候,那么k个色子和为sum的次数应该是k-1个色子在和为这几个数字时候的次数相加。
因此,可以定义2个数组来分别存储k-1个色子时候的各个和的次数以及k各色子时候各个和的次数,而k个色子时候各个和的次数是通过k-1个色子时候的几个和的次数相加得到的。
其操作过程为:
一个大循环,以色子数目作为循环条件,从第一个色子算起,
在大循环内再以当前色子数目对应的和的范围作为循环条件,计算每个和出现的次数,而出现次数的计算是通过色子掷出的数字为循环:即1~6,相应在存储k-1个色子各个和出现次数的数组中取出对应的次数,累加得到当前色子数相应和出现的次数。
另外需要注意的是,当新增加一个色子的时候,k个色子所对应的和的取值范围中,并不是每个值都是通过k-1个色子时候的6个值的次数相加得到,因为:
k-1个色子的和的范围是k-1~6(k-1),而k个色子的和的范围是k~6k:
k-1、k、k+1、k+2、k+3、k+4……、6k-7、6k-6;
k、k+1、k+2、k+3、k+4……、6k-7、6k-6、6k-5、6k-4、6k-3、6k-2、6k-1、6k
对于k个色子时候的前几个值,色子掷出数字的循环不应该是1~6,
如:和为k的时候,掷出1,k-1个色子对应和只能是k-1;和为k+1的时候,掷出1、2,k-1个色子对应和只能是k和k-1;
以此类推前5个值其循环都是小于6的,且由上面规律可知:
// 第一个和,循环为k-(k-1)==1
// 第二个和,循环为k+1-(k-1)==2…….
// 可知,循环为:当前待求和-当前色子数-1
// 也就是在k个色子时候,循环求某一和出现次数的时候
// 掷出点数的循环应该同时满足小于6和小于上面这一条件

void PrintProbability_Solution2(int n)
{
    if (n == 0)
    {
        return;
    }
    //因为要从只有一个色子开始计算,因而和的值也是从1开始算
    //的,所以数组长度应为最大的那个数
    int SumsScale = Biggest_Value*n;  
    int* Times1 = new int[SumsScale];
    int* Times2 = new int[SumsScale];
    memset(Times1, 0, SumsScale*sizeof(int));
    memset(Times2, 0, SumsScale*sizeof(int));
    //初始化只有一个色子的时候
    for (int i = 0; i < Biggest_Value;i++)     
    {
        Times1[i] = 1;
    }
    //色子个数的循环
    for (int i = 2; i <= n;i++)       
    {
        //以和的范围为循环,计算加入第i个色子后各个和的出现次数
        for (int sum = i; sum <=Biggest_Value*i;sum++)      
        {
        //对于i个色子时候的某一和,根据i-1个色子时候来计算
            for (int j = 1; j <= sum - i + 1 && j <=  
                  Biggest_Value;j++)      
            {
                Times2[sum - 1] += Times1[sum - j - 1];
            }
        }
        //每次得到k个色子下的各个和的次数后,进入下一个循环(增加
        //一个色子)前,应该先把存k个色子数据的数组值赋值给
        //存k-1个色子数据的数组中,并且将k个色子中的数据归零,
        //应为k数组是对k-1数组的累加,如果不清零则产生错误
        for (int sum = i; sum <= Biggest_Value*i; sum++)
        {
            Times1[sum - 1] = Times2[sum - 1];
            Times2[sum - 1] = 0; // 之前未清零导致错误。                       
        }
    }
    for (int i = n; i <= SumsScale;i++)
    {
        cout << i << ":" << 
        Times1[i - 1]/pow(Biggest_Value, n) << endl;
    }
    delete []Times1;
    delete []Times2;
}

// ====================测试代码====================

void Test(int n)
{
    printf("Test for %d begins:\n", n);

    printf("Test for solution1\n");
    PrintProbability_Solution1(n);
    cout << "-----------" << endl;
    printf("Test for solution2\n");
    PrintProbability_Solution2(n);
    printf("\n");
}

int main(int argc, char* argv[])
{
    Test(1);
    Test(2);
    Test(3);
    Test(4);
    Test(5);
    Test(11);
    Test(0);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

弹指间LDL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值