题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s,输入n,打印出s的所有可能的值出现的概率。
解法一:基于递归求骰子点数,时间效率不高。
现在我们考虑如何统计每一个点数出现的次数.要想求出n个骰子的点数和,可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个,单独的那一个有可能出现从1到6的点数.我们需要计算从1到6的每一种点数和剩下的n-1个骰子来计算点数和.接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰子的点数和这一轮单独筛子的点数相加,再和剩下的n-2个骰子来计算点数和。递归结束的条件就是最后只剩下一个骰子。
基于这种思路,代码参考如下:
1 int g_maxValue=6; 2 void PrintProbability(int number) 3 { 4 if(number<1) 5 return; 6 int maxSum=num*g_maxValue; 7 int* pProbabilities=new int[maxSum-number+1]; 8 for(int i=number;i<=maxSum;++i) 9 pProbabilities[i-number]=0; 10 Probability(number,pProbabilities); 11 int total=pow((double)g_maxValue,number); 12 for(int i=number;i<=maxSum;++i) 13 { 14 double ratio=(double)pProbabilities[i-number]/total; 15 printf("%d:%e ",i,ratio); 16 } 17 delete []probabilities; 18 } 19 20 void Probability(int number,int* pProbabilities) 21 { 22 for(int i=1;i<=g_maxValue;++i) 23 Probability(number,number,i,pProbabilities); 24 } 25 26 void Probability(int original,int current,int sum,int* pProbabilities) 27 { 28 if(current==1) 29 { pProbabilities[sum-original]++; } 30 else 31 { 32 for(int i=1;i<=g_maxValue;++i) 33 {Probabilitiey(original,current-1,i+sum,pProbabilities);} 34 } 35 }
上述思路很简洁,实现起来很容易。但由于是基于递归实现,它有很多的计算是重复的,从而导致当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之和。基于这个思路,参考代码如下:
1 int g_maxValue=6; 2 void PrintProbability(int number) 3 { 4 if(number<1) return; 5 int *pProbabilities[2]; 6 pProbabilities[0]= new int[g_maxValue*number+1]; 7 pProbabilities[1]=new int[g_maxValue*number+1]; 8 for(int i=0;i<g_maxValue*number+1;++i) 9 { 10 pProbabilities[0][i]=0; 11 pProbabilities[1][i]=0; 12 } 13 14 int flag=0; 15 for(int i=1;i<=g_maxValue;++i) 16 pProbabilities[flag][i]=1; 17 18 for(int k=2;k<=number;++k) 19 { 20 for(int i=0;i<k;++i) 21 pProbabilities[1-flag][i]=0; 22 23 for(int i=k;i<=g_maxValue*k;++i) 24 { 25 pProbabilities[1-flag][i]=0; 26 for(int j=1;j<=i&&j<=g_maxValue;++j) 27 pProbabilities[1-flag][i] +=pProbabilities[flag][i-j]; 28 } 29 flag=1-flag; 30 } 31 32 double total=pow((double)g_maxValue,number); 33 for(int i=number;i<=g_maxValue*number;++i) 34 { 35 double ratio=(double)pProbabilities[flag][i]/total; 36 printf("%d:%e ",i,ratio); 37 } 38 delete []pProbabilities[0]; 39 delete []pProbabilities[1]; 40 }
在上述代码中,我们定义了两个数组pProbabilities[0]和pProbabilities[1]来存储骰子的点数之和。在一轮循环中,一个数组的第n项等于另一个数组的第n-1,n-2,n-3,n-4,n-5,n-6项的和。在下一轮循环中,我们交换这两个数组(通过改变变量flag实现)再重复这一计算过程。
值得注意的是,上述代码没有在函数里把一个骰子的最大点数硬编码为6,而是用一个变量g_maxValue来表示。这样做的好处是如果某个厂家生产了其他点数的骰子,我们只需要在代码中修改一个地方,扩展起来很方便。