剑指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)考查对递归和循环的性能的理解。
- 剑指offer-6-面试43:n 个骰子的点数()
- 剑指offer面试题43 n个骰子的点数
- 【剑指offer】面试题43:n个骰子的点数
- 剑指Offer:面试题43 n个骰子的点数
- 剑指offer 43-n个骰子的点数
- 面试题43:n个骰子的点数(《剑指offer》)
- 剑指offer-面试题43:n个骰子的点数
- 【剑指offer-Java版】43n个骰子的点数
- 《剑指offer》:[43]N个骰子的点数
- 剑指offer面试题43:n个骰子的点数
- 《剑指offer》43:n个骰子的点数
- 剑指offer-面试题43-n个骰子的点数
- 剑指offer 面试题43 n个骰子的点数
- 【剑指offer】题43:n个骰子的点数
- 剑指offer(47):n个骰子的点数
- 剑指offer:n个骰子的点数(java)
- 剑指Offer面试题43(Java版):n个骰子的点数
- 剑指Offer面试题43(Java版):n个骰子的点数
- Servlet的调用过程和生命周期
- ios开发-获取手机相关信息
- python字典复制(浅拷贝and深拷贝)
- 物联网加密
- HDU 3328 Flipper(栈模拟)
- 剑指offer-6-面试43:n 个骰子的点数()
- deepin 系统中MongoDB的安装
- VC维
- SVN服务器和客户端的安装以及在MyEclipse中的配置
- fatal error C1004unexpected end of file found
- android proguard使用心得和遇到的若干问题以及解决思路
- 将Assets中所有目录和文件,都复制到SD卡中
- GuavaCache使用笔记
- ListView分页加载数据