《剑指offer》:[43]N个骰子的点数
2016-06-25 11:22
330 查看
题目:把N个骰子扔在地上,所有骰子朝上一面的点数之和为S,输入N,打印出S的所有可能的值出现的概率。
![](https://img-blog.csdn.net/20160625111857406?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
分析:对于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的次数之和。这样用一个数组来记录上一步的结果,而下一步的结果要用到上一步的结果。该方法避免了复杂多余的计算。
具体实现代码如下:
运行结果:
![](https://img-blog.csdn.net/20160625111957891?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
由于该方法有许多地方的计算是重复的,和前面讲到过的【9】斐波拉切数列以及【39-1】中判断是否为平衡二叉树一样,存在重复计算导致效率低下。
所以下面采用一种循环的方式来解决此问题,也是时间换空间的方法。
方案二代码实现:
运行结果:
![](https://img-blog.csdn.net/20160625112211142?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
此种方法的时间效率相对于方案有所提高,但是借助了辅助空间。
分析:对于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; }
运行结果:
此种方法的时间效率相对于方案有所提高,但是借助了辅助空间。
相关文章推荐
- 提高JavaScript性能
- CSS权威指南-伪元素选择器
- JavaScript 与html的元素产生关联
- javascript标准对象
- jquery实现上传文件大小类型的验证例子(推荐)
- String、StringBuffer类
- css块级元素居中
- React Native技术周报一
- 自定义 jstl 标签
- React-nwb的使用
- jQuery实现对无序列表的排序功能(附demo源码下载)
- rapidjson的read和write的sample
- js面向对象
- ASP.NET MVC+Bootstrap个人博客之praise.js点赞特效插件
- jquery中的ready与load的区别
- JSP/Servlet 工作原理
- 关于Bootstrap的整理和理解
- js数组的声明与应用
- Js内置对象的应用
- JavaScript对象