剑指offer-6-面试43:n 个骰子的点数()

来源:互联网 发布:linux acl权限控制 编辑:程序博客网 时间:2024/05/17 06:39

  • 题目
  • 分析
    • 解法一基于递归求骰子点数时间效率不够高
    • 基于循环求骰子点数时间性能好
  • 测试用例代码
  • 本题考点

题目

把 n 个骰子扔在地上,所有骰子朝上一面的点数之和为 s。 输入 n,打印出 s 的 所有可能的值出现的概率。

骰子对应的点数是1-6之间的一个数字。所以,n 个骰子的点数和的最小值为 n,最大值为 6n。另外根据排列组合的知识,还知道n个骰子的所有点数的排列数为 6^n。要解决这个问题,需要先统计出每一个点数出现的次数,然后把每一个点数出现的次数除以 6^n,就能求出每个点数出现的概率。

分析

解法一:基于递归求骰子点数,时间效率不够高

考虑如何统计每一个点数出现的次数。要想求出n个骰子的点数和,可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从 1到 6的点数。我们需要计算从 1到6的每一种点数和剩下的 n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有 n-2 个。我们把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的 n-2 个骰子来计算点数和。分析到这里,不难发现是一种递归的思路,递归结束的条件就是最后只剩下一个骰子。

可以定义一个长度为 6n-n+1 的数组,和为 s的点数出现的次数保存到数组第 s-n 个元素里。

int g_maxValue = 6;void PrintProbability_Solution1(int number){    if(number < 1)        return;    int maxSum = number * g_maxValue;    int* pProbabilities = new int[maxSum - number + 1];    for(int i = number; i <= maxSum; ++i)        pProbabilities[i - number] = 0;    Probability(number, pProbabilities);    int total = pow((double)g_maxValue, number);    for(int i = number; i <= maxSum; ++i)    {        double ratio = (double)pProbabilities[i - number] / total;        printf("%d: %e\n", i, ratio);    }    delete[] pProbabilities;}void Probability(int number, int* pProbabilities){    for(int i = 1; i <= g_maxValue; ++i)        Probability(number, number, i, pProbabilities);}void Probability(int original, int current, int sum,                  int* pProbabilities){    if(current == 1)    {        pProbabilities[sum - original]++;    }    else    {        for(int i = 1; i <= g_maxValue; ++i)        {            Probability(original, current - 1, i + sum, pProbabilities);        }    }} 

上述思路简洁,实现起来也容易。但由于是基于递归的实现,有很多计算是重复的,从而导致当number变大时性能慢得让人不能接受。

基于循环求骰子点数,时间性能好

可以换一种思路来解决这个问题。可以考虑用两个数组来存储骰子点数的每一个总数出现的次数。在一次循环中,第一个数组中的第 n 个数字表示骰子和为 n 出现的次数。在下一循环中,加上一个新的骰子,此时和为n 的骰子出现的次数应该等于上一次循环中骰子点数和为 n-1、n-2、n-3、n-4、n-5 与 n-6的次数的总和,所以我们把另一个数组的第 n 个数字设为前一个数组对应的 第 n-1、n-2、n-3、n-4、n-5 与 n-6之和。

// ====================方法二====================void PrintProbability_Solution2(int number){    if(number < 1)        return;    int* pProbabilities[2];    pProbabilities[0] = new int[g_maxValue * number + 1];    pProbabilities[1] = new int[g_maxValue * number + 1];    for(int i = 0; i < g_maxValue * number + 1; ++i)    {        pProbabilities[0][i] = 0;        pProbabilities[1][i] = 0;    }    int flag = 0;    for (int i = 1; i <= g_maxValue; ++i)         pProbabilities[flag][i] = 1;     for (int k = 2; k <= number; ++k)     {        for(int i = 0; i < k; ++i)            pProbabilities[1 - flag][i] = 0;        for (int i = k; i <= g_maxValue * k; ++i)         {            pProbabilities[1 - flag][i] = 0;            for(int j = 1; j <= i && j <= g_maxValue; ++j)                 pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];        }        flag = 1 - flag;    }    double total = pow((double)g_maxValue, number);    for(int i = number; i <= g_maxValue * number; ++i)    {        double ratio = (double)pProbabilities[flag][i] / total;        printf("%d: %e\n", i, ratio);    }    delete[] pProbabilities[0];    delete[] pProbabilities[1];}

在上述代码中,我们定义了两个数组 pProbabilities[0] 和 pProbabilities[1] 来存储骰子的点数之和。在一轮循环中,一个数组的第 n 项等于另一数组的第 n-1、n-2、n-3、n-4、n-5 以及 n-6项的和。 在下一轮循环中,我们交换这两个数组(通过改变变量 flag 实现)再重复这一计算过程。

值得注意的是,上述代码没有在函数里把一个骰子的最大点数硬编码(hard code)为 6,而是用一个变量 g_maxValue来表示。这样做的好处是,如果某个厂家生产了其他点数的骰子,我们只需要在代码中修改一个地方,扩展起来方便。如果在面试的时候我们能对面试官提起对程序扩展性的考虑,一定能给面试官留下很好的印象。

测试用例&代码

(1)功能测试(1、2、3、4个骰子的各点数的概率)

(2)特殊输入测试(输入0)

(3)性能测试(输入较大的数字,比如 11)

本题考点

(1)数学建模的能力。不管采用哪种思路解决问题,我们都要先想到用数组来存放n个骰子的每一个点数出现的次数,并通过分析点数的规律建立模型并最终找到解决方案。

(2)考查对递归和循环的性能的理解。

0 1
原创粉丝点击