题目: 把n个骰子扔在地上,记所有骰子朝上一面的点数和为s。输入n,求所有可能出现的s的概率
题目分析:
常规的骰子各个面的点数分别为1,2,3,4,5,6。因此比较直观的一种解法是,每次模拟一次骰子可能出现的各个情况,然后递归遍历剩下n-1个骰子的情况,代码实现如下
/* * 使用递归的方式实现 * 也即模拟每一个骰子的点数,当模拟到最后一个的时候,即为所得和 * */ public static void posibilitySimple(int n){ //定义数组用于存放每个和可能出现的情况 //可能出现的和从n到6*n int[]sums= new int[maxValue*n+1]; //将统计值初始化 for(int i=0;i<maxValue*n+1;i++){ sums[i]=0; } //进行递归处理 doPosibilitySimple(sums,0,n); double[]posibilities = new double[maxValue*n+1]; double totoalSum = Math.pow((double) maxValue,(double) n); for(int i=0;i<maxValue*n+1;i++){ posibilities[i]=sums[i]/totoalSum; } System.out.println(Arrays.toString(posibilities)); } private static void doPosibilitySimple(int[]sums,int curSum,int n){ if(n==0){ sums[curSum]++; return; } else{ for(int i=1;i<=maxValue;i++){ doPosibilitySimple(sums,curSum+i,n-1); } } }
由于使用的是递归的算法,因此当n为10左右,速度就不太行了,根本原因还是进行了一些重复的计算
因此,继续想办法减少重复计算,减少重复计算的思路,当然是要从缓存计算结果出发
再继续分析投n个骰子的情况
当投第一个骰子时,可能出现的结果为1-6
当投第二个骰子时,可能出现的结果为为2-12,
其中2出现的次数等于第一个骰子出现1的次数(第二个骰子只能是1)
其中3出现的次数等于第一个骰子出现2的次数加上第一个骰子出现1的次数(第二个骰子可能是1,可能是2)
....
其中12出现的次数等于第一个骰子出现6的次数(第二个骰子只能是6)
因此可以得出如下等式
F[i][n]=F[i-1][n-1]+F[i-1][n-2]+...+F[i-1][n-6]
因此,从第一个骰子开始,保存每一个骰子的计算结果
再接着分析,由于计算第n个骰子的时候,只需要用到第n-1个骰子的结果
因此保存结果的范围可以缩小到两个骰子,因此可以用两个数组来缓存结果
实现代码如下
/* * 使用快速的方法计算 * 思路是当骰子个数为k时,和为s的情况为 * 前k-1个骰子和为s-1(第k个为1),和为s-2(第k个为2)...的总数相加 * 因此可以定义两个数组,用于交叉计算 * 模拟从第一个到第n个骰子的情况 * */ public static void posibilityFast(int n){ int[][] sums = new int[2][maxValue*n+1]; //初始化 for(int i=0;i<maxValue*n+1;i++){ sums[0][i]=0; sums[1][i]=0; } //用于标识当前使用哪个数组 int flag = 0; for(int i=1;i<=maxValue;i++){ sums[flag][i]=1; } flag=(flag+1)&1; //对每一个骰子进行模拟 for(int i=2;i<=n;i++){ //第i个骰子值的范围为i到maxValue*i //也即i之前的都需要置为0 for(int j=0;j<i;j++){ sums[flag][j]=0; } //依次处理第i个开始到maxValue*i for(int j=i;j<=maxValue*i;j++){ //sums[flag][j]=sums[(flag+1)&1][j-1]+...+ //先清除之前的数据 sums[flag][j]=0; //重新计算 int k=1; while((k<=maxValue)&&(j-k)>=0){ sums[flag][j]+=sums[(flag+1)&1][j-k]; k++; } } flag = (flag+1)&1; } flag = (flag+1)&1; double[]posibilities = new double[maxValue*n+1]; double totoalSum = Math.pow((double) maxValue,(double) n); for(int i=0;i<maxValue*n+1;i++){ posibilities[i]=sums[flag][i]/totoalSum; } System.out.println(Arrays.toString(posibilities)); }