【Java】 剑指offer(60) n个骰子的点数
本文参考自《剑指offer》一书,代码采用Java语言。
题目
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
思路
对于n个骰子,要计算出每种点数和的概率,我们知道投掷n个骰子的总情况一共有6^n种,因此只需要计算出某点数和的情况一共有几种,即可求出该点数之和的概率。
方法一:基于递归的方法,效率较低
易知,点数之和s的最小值为n,最大值为6*n,因此我们考虑用一个大小为(6*n-n+1)的数组存放不同点数之和的情况个数,那么,如果点数之和为x,那么把它出现的情况总次数放入数组种下标为x-n的元素里。
确定如何存放不同点数之和的次数后,我们要计算出这些次数。我们把n个骰子分为1个骰子和n-1个骰子,这1
个骰子可能出现1~6个点数,由该骰子的点数与后面n-1个骰子的点数可以计算出总点数;而后面的n-1个骰子又可以分为1个和n-2个,把上次的点数,与现在这个骰子的点数相加,再和剩下的n-2个骰子的点数相加可以得到总点数……,即可以用递归实现。在获得最后一个骰子的点数后可以计算出几个骰子的总点数,令数组中该总点数的情况次数+1,即可结束遍历。
方法二:基于循环求骰子点数,时间性能好
用数组存放每种骰子点数和出现的次数。令数组中下标为n的元素存放点数和为n的次数。我们设置循环,每个循环多投掷一个骰子,假设某一轮循环中,我们已知了各种点数和出现的次数;在下一轮循环时,我们新投掷了一个骰子,那么此时点数和为n的情况出现的次数就等于上一轮点数和为n-1,n-2,n-3,n-4,n-5,n-6的情况出现次数的总和。从第一个骰子开始,循环n次,就可以求得第n个骰子时各种点数和出现的次数。
我们这里用两个数组来分别存放本轮循环与下一轮循环的各种点数和出现的次数,不断交替使用。
测试算例
1.功能测试(1,2,3,4个骰子)
2.特殊测试(0个)
3.性能测试(11个)
Java代码
import java.text.NumberFormat; //题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s //的所有可能的值出现的概率。 public class DicesProbability { private static final int maxValue = 6; /** * 方法一:递归解法 */ public static void printProbability1(int number) { if(number<=0) return; //错误 int[] probabilities = new int[maxValue*number-number+1]; //下标为i,对应的值代表点数之和为i+number总共出现的情况次数 //点数从number~maxValue*number,所以数组大小为6*number-number+1 for(int i=0;i<probabilities.length;i++) probabilities[i]=0; //计算不同点数出现的次数 for(int i=1;i<=maxValue;i++) calP(probabilities,number,number-1,i); //第一次掷骰子,总点数只能是1~maxValue(即6) int totalP = (int) Math.pow(maxValue, number); //所有情况总共出现的次数 for( int i=0;i<probabilities.length ;i++) { double ratio = (double)probabilities[i]/totalP; NumberFormat format = NumberFormat.getPercentInstance(); format.setMaximumFractionDigits(2);//设置保留几位小数 System.out.println("点数和为"+(i+number)+"的概率为:"+format.format(ratio)); } } /** * 计算每种点数出现的次数 * @param number:骰子总个数 * @param curNumber:当前剩余骰子个数 * @param sum:各个骰子加起来的总点数 */ private static void calP(int[] probabilities, int number, int curNumber, int sum) { if(curNumber==0) { probabilities[sum-number]++; //总数为sum的情况存放在sum-number下标中 return; } for(int i=1;i<=maxValue;i++) calP(probabilities, number, curNumber-1, sum+i); //相当于剩余的骰子少一个,总点数增加。 } //=========================================== /** * 方法二:基于循环求骰子点数,时间性能好 */ public static void printProbability2(int number) { if(number<=0) return; //错误 int[][] probabilities = new int[2][number*maxValue+1]; //[2]代表用两个数组交替保存,[number*maxValue+1]是指点数为所在下标时,该点数出现的总次数。 //probabilities[*][0]是没用的,只是为了让下标对应点数 for(int i=0;i<2;i++) { for(int j=0;j<number*maxValue;j++) { probabilities[i][j]=0; } } for(int i=1;i<=6;i++) probabilities[0][i]=1; //第一个骰子出现的情况 int flag=0; for(int curNumber=2;curNumber<=number;curNumber++) { //当前是第几个骰子 for(int i=0;i<curNumber;i++) probabilities[1-flag][i]=0; //前面的数据清零 for(int i=curNumber;i<=curNumber*maxValue;i++) { for(int j=1;j<=6 && j<=i ;j++) { probabilities[1-flag][i]+=probabilities[flag][i-j]; } } flag=1-flag; } int totalP = (int) Math.pow(maxValue, number); //所有情况总共出现的次数 for( int i=number;i<= number*6;i++) { double ratio = (double)probabilities[flag][i]/totalP; NumberFormat format = NumberFormat.getPercentInstance(); format.setMaximumFractionDigits(8);//设置保留几位小数 System.out.println("点数和为"+(i+number)+"的概率为:"+format.format(ratio)); } } public static void main(String[] args) { System.out.println("=========方法一============"); for(int i=0;i<=3;i++) { System.out.println("-----骰子数为"+i+"时-----"); printProbability1(i); } System.out.println("-----骰子数为"+11+"时-----"); printProbability1(11); System.out.println("=========方法二============"); for(int i=0;i<=3;i++) { System.out.println("-----骰子数为"+i+"时-----"); printProbability2(i); } System.out.println("-----骰子数为"+11+"时-----"); printProbability1(11); } }
=========方法一============ -----骰子数为0时----- -----骰子数为1时----- 点数和为1的概率为:16.66666667% 点数和为2的概率为:16.66666667% 点数和为3的概率为:16.66666667% 点数和为4的概率为:16.66666667% 点数和为5的概率为:16.66666667% 点数和为6的概率为:16.66666667% -----骰子数为2时----- 点数和为2的概率为:2.77777778% 点数和为3的概率为:5.55555556% 点数和为4的概率为:8.33333333% 点数和为5的概率为:11.11111111% 点数和为6的概率为:13.88888889% 点数和为7的概率为:16.66666667% 点数和为8的概率为:13.88888889% 点数和为9的概率为:11.11111111% 点数和为10的概率为:8.33333333% 点数和为11的概率为:5.55555556% 点数和为12的概率为:2.77777778% -----骰子数为3时----- 点数和为3的概率为:0.46296296% 点数和为4的概率为:1.38888889% 点数和为5的概率为:2.77777778% 点数和为6的概率为:4.62962963% 点数和为7的概率为:6.94444444% 点数和为8的概率为:9.72222222% 点数和为9的概率为:11.57407407% 点数和为10的概率为:12.5% 点数和为11的概率为:12.5% 点数和为12的概率为:11.57407407% 点数和为13的概率为:9.72222222% 点数和为14的概率为:6.94444444% 点数和为15的概率为:4.62962963% 点数和为16的概率为:2.77777778% 点数和为17的概率为:1.38888889% 点数和为18的概率为:0.46296296% -----骰子数为11时----- 点数和为11的概率为:0.00000028% 点数和为12的概率为:0.00000303% 点数和为13的概率为:0.00001819% 点数和为14的概率为:0.00007883% 点数和为15的概率为:0.00027591% 点数和为16的概率为:0.00082774% 点数和为17的概率为:0.00220426% 点数和为18的概率为:0.00532722% 点数和为19的概率为:0.01186118% 点数和为20的概率为:0.02459557% 点数和为21的概率为:0.04789041% 点数和为22的概率为:0.08811621% 点数和为23的概率为:0.15397396% 点数和为24的概率为:0.25654646% 点数和为25的概率为:0.40891953% 点数和为26的概率为:0.6252344% 点数和为27的概率为:0.91910173% 点数和为28的概率为:1.30143669% 点数和为29的概率为:1.77793036% 点数和为30的概率为:2.34652097% 点数和为31的概率为:2.99533825% 点数和为32的概率为:3.70163009% 点数和为33的概率为:4.43211149% 点数和为34的概率为:5.14496733% 点数和为35的概率为:5.79345109% 点数和为36的概率为:6.33070903% 点数和为37的概率为:6.71518156% 点数和为38的概率为:6.91574824% 点数和为39的概率为:6.91574824% 点数和为40的概率为:6.71518156% 点数和为41的概率为:6.33070903% 点数和为42的概率为:5.79345109% 点数和为43的概率为:5.14496733% 点数和为44的概率为:4.43211149% 点数和为45的概率为:3.70163009% 点数和为46的概率为:2.99533825% 点数和为47的概率为:2.34652097% 点数和为48的概率为:1.77793036% 点数和为49的概率为:1.30143669% 点数和为50的概率为:0.91910173% 点数和为51的概率为:0.6252344% 点数和为52的概率为:0.40891953% 点数和为53的概率为:0.25654646% 点数和为54的概率为:0.15397396% 点数和为55的概率为:0.08811621% 点数和为56的概率为:0.04789041% 点数和为57的概率为:0.02459557% 点数和为58的概率为:0.01186118% 点数和为59的概率为:0.00532722% 点数和为60的概率为:0.00220426% 点数和为61的概率为:0.00082774% 点数和为62的概率为:0.00027591% 点数和为63的概率为:0.00007883% 点数和为64的概率为:0.00001819% 点数和为65的概率为:0.00000303% 点数和为66的概率为:0.00000028% =========方法二============ -----骰子数为0时----- -----骰子数为1时----- 点数和为2的概率为:16.66666667% 点数和为3的概率为:16.66666667% 点数和为4的概率为:16.66666667% 点数和为5的概率为:16.66666667% 点数和为6的概率为:16.66666667% 点数和为7的概率为:16.66666667% -----骰子数为2时----- 点数和为4的概率为:2.77777778% 点数和为5的概率为:5.55555556% 点数和为6的概率为:8.33333333% 点数和为7的概率为:11.11111111% 点数和为8的概率为:13.88888889% 点数和为9的概率为:16.66666667% 点数和为10的概率为:13.88888889% 点数和为11的概率为:11.11111111% 点数和为12的概率为:8.33333333% 点数和为13的概率为:5.55555556% 点数和为14的概率为:2.77777778% -----骰子数为3时----- 点数和为6的概率为:0.92592593% 点数和为7的概率为:1.85185185% 点数和为8的概率为:3.24074074% 点数和为9的概率为:5.09259259% 点数和为10的概率为:6.94444444% 点数和为11的概率为:9.72222222% 点数和为12的概率为:11.57407407% 点数和为13的概率为:12.5% 点数和为14的概率为:12.5% 点数和为15的概率为:11.57407407% 点数和为16的概率为:9.72222222% 点数和为17的概率为:6.94444444% 点数和为18的概率为:4.62962963% 点数和为19的概率为:2.77777778% 点数和为20的概率为:1.38888889% 点数和为21的概率为:0.46296296% -----骰子数为11时----- 点数和为22的概率为:0.00121693% 点数和为23的概率为:0.00298376% 点数和为24的概率为:0.00638621% 点数和为25的概率为:0.01258472% 点数和为26的概率为:0.02324523% 点数和为27的概率为:0.04090248% 点数和为28的概率为:0.06852398% 点数和为29的概率为:0.11056181% 点数和为30的概率为:0.17250802% 点数和为31的概率为:0.26102196% 点数和为32的概率为:0.38391023% 点数和为33的概率为:0.54975226% 点数和为34的概率为:0.76760849% 点数和为35的概率为:1.04620281% 点数和为36的概率为:1.39300386% 点数和为37的概率为:1.81311477% 点数和为38的概率为:2.30801735% 点数和为39的概率为:2.87442713% 点数和为40的概率为:3.50323763% 点数和为41的概率为:4.17879659% 点数和为42的概率为:4.87880723% 点数和为43的概率为:5.57487572% 点数和为44的概率为:6.23383532% 点数和为45的概率为:6.8198307% 点数和为46的概率为:7.29713005% 点数和为47的概率为:7.63343598% 点数和为48的概率为:7.80322374% 点数和为49的概率为:7.79077491% 点数和为50的概率为:7.59241029% 点数和为51的概率为:7.21750041% 点数和为52的概率为:6.68797654% 点数和为53的概率为:6.03632186% 点数和为54的概率为:5.3023148% 点数和为55的概率为:4.52891823% 点数和为56的概率为:3.75794284% 点数和为57的概率为:3.02613646% 点数和为58的概率为:2.3622405% 点数和为59的概率为:1.78534552% 点数和为60的概率为:1.3046269% 点数和为61的概率为:0.92032941% 点数和为62的概率为:0.62564372% 点数和为63的概率为:0.40903116% 点数和为64的概率为:0.25656879% 点数和为65的概率为:0.15397644% 点数和为66的概率为:0.08811621% 点数和为67的概率为:0.04789041% 点数和为68的概率为:0.02459557% 点数和为69的概率为:0.01186118% 点数和为70的概率为:0.00532722% 点数和为71的概率为:0.00220426% 点数和为72的概率为:0.00082774% 点数和为73的概率为:0.00027591% 点数和为74的概率为:0.00007883% 点数和为75的概率为:0.00001819% 点数和为76的概率为:0.00000303% 点数和为77的概率为:0.00000028%
收获
1.int类型相除,要得到double类型,需要提前将其中一个变成double类型
例如:double ratio = (double)probabilities[i]/totalP;
2.输出百分数的方法,利用NumberFormat
NumberFormat format = NumberFormat.getPercentInstance(); format.setMaximumFractionDigits(8);//设置保留几位小数 System.out.println("点数和为"+(i+number)+"的概率为:"+format.format(ratio));
3.第二种方法,不是骰子点数的角度出发,而是从点数之和出发,点数之和有:f(n)=f(n-1)+……f(n-6),非常巧妙。
4.用两个数组交替存放,学会使用变量flag,flag=1-flag。
5.代码中没有把骰子的最大点数硬编码为6,而是用变量maxValue来表示,具有可拓展性。以后自己编程时也要注意这些量是否可以不用硬编码,从而提高扩展性。
6.提高数学建模能力,不管采取哪种思路,都要先想到用数组来存放n个骰子的每个点数和出现的次数。