您的位置:首页 > Web前端

《剑指offer》:[43]N个骰子的点数

2016-06-25 11:22 330 查看
题目:把N个骰子扔在地上,所有骰子朝上一面的点数之和为S,输入N,打印出S的所有可能的值出现的概率。



分析:对于6点的N个骰子来讲,其和S的最小值为N,最大值为6N。要得到和S出现的概率,就得得到和S出现的次数,然后用某一S出现的次数/S的所有可能性=S出现的概率。对于骰子进行全排列,我们很容易知道S的所有可能性为:6^N。那么问题来了,怎么计算出S出现的次数呢?
方案一:递归法。由于不能一口气吃一个大胖子,所以我们必须把事情一步步的解决。该方法的思路是将N个骰子分成两部分,第一部分是一个骰子,另一部分是剩下的N-1个。我们先计算第一部分第一个骰子出现的点数,很明显,有1-6这6种可能性;然后再计算这一已知的部分和剩下的N-1个骰子出现的点数之和。仿照前例,我们可以将第二部分分解为第一部分(1个)和第二部分(剩下的N-2)。我们把上一轮和这里的第一部分的和相加,得到新的点数之和,再将新的点数之和和剩下的N-2部分进行加法计算出新的点数之和。这就是典型的递归思想。结束条件是只剩下最后一个骰子。
方案二:循环法。该方法的主要思路是用两个数组来求的骰子值可能出现的次数,是一种空间换时间的做法。关键思想是:在一次循环中,第一个数组中的第N个数字表示骰子和为N出现的次数。下一次循环中,我们加上一个新的骰子,此时和为S的骰子出现的次数应该等于第一个数组中S-1,S-2,S-3,S-4,S-5,S-6的次数之和。这样用一个数组来记录上一步的结果,而下一步的结果要用到上一步的结果。该方法避免了复杂多余的计算。

具体实现代码如下:
#include <iostream>
#include <math.h>
#include <stdlib.h>
using namespace std;
int MaxValue=6;
void Probabilityhelp(int original,int current,int sum,int *probabilitys)
{
if(current==1)//当只剩下最后一个骰子的时候,计算其和;
{
probabilitys[sum-original]++;//和为sum的数组+1;
}
else
{
for(int i=1;i<=MaxValue;i++)
{
Probabilityhelp(original,current-1,sum+i,probabilitys);//剩下的继续递归;
}
}
}
void Probability(int number,int *pProbability)
{
for(int i=1;i<=MaxValue;i++)
Probabilityhelp(number,number,i,pProbability);
}
void PrintProbability(int number)
{
if(number<1)
return;
int MaxSum=number*MaxValue;
int *pProbability=new int[MaxSum-number+1];//我们定义一个6N-N+1的数组,和为S的点数出现的次数保存到数组第S-N个元素里。
for(int i=number;i<=MaxSum;i++)
pProbability[i-number]=0;//次数都初始化为0次;
Probability(number,pProbability);
int total=pow((double)MaxValue,number);//总共出现的值的可能性;
for(int i=number;i<=MaxSum;i++)
{
double ratio=(double)pProbability[i-number]/total;//出现的次数/总的可能性;
cout<<i<<" "<<ratio<<endl;
}
int a=1;
int b=2;
delete []pProbability;
}

int main()
{
PrintProbability(6);
system("pause");
return 0;
}

运行结果:



由于该方法有许多地方的计算是重复的,和前面讲到过的【9】斐波拉切数列以及【39-1】中判断是否为平衡二叉树一样,存在重复计算导致效率低下。

所以下面采用一种循环的方式来解决此问题,也是时间换空间的方法。

方案二代码实现:
#include <iostream>
#include <math.h>
using namespace std;
int g_maxValue=6;
void PrintProbability(int n)
{
if(n<1)
return;
int* pProbability[2];
pProbability[0]=new int[g_maxValue*n+1];
pProbability[1]=new int[g_maxValue*n+1];
for(int i=0;i<=g_maxValue*n;i++)
{
pProbability[0][i]=0;
pProbability[1][i]=0;
}
int flag=0;
for(int i=1;i<=g_maxValue;i++)
pProbability[flag][i]=1;
for(int k=2;k<=n;k++)
{
for(int i=0;i<k;i++)
pProbability[1-flag][i]=0;
for(int i=k;i<=g_maxValue*k;i++)
{
pProbability[1-flag][i]=0;
for(int j=1;j<=i && j<=g_maxValue;j++)
pProbability[1-flag][i]+=pProbability[flag][i-j];//这一步就是求和为S时的次数为上一步S-1,S-2,S-3,S-4,S-5,S-6的总次数;
}
flag=1-flag;
}
int total=pow((double)g_maxValue,n);
double prob=0;
for(int i=n;i<=g_maxValue*n;i++)
{
double ratio=(double)pProbability[flag][i]/total;
prob+=ratio;
cout<<i<<" "<<ratio<<" "<<endl;
}
cout<<"校验和是否1:"<<prob<<endl;
cout<<endl;
delete[] pProbability[0];
delete[] pProbability[1];
}

int main()
{
PrintProbability(2);
system("pause");
return 0;
}

运行结果:



此种方法的时间效率相对于方案有所提高,但是借助了辅助空间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: