面试题:n个骰子的点数
题目:把n个骰子扔在地上,所有骰子朝上一面的点数之和为s,输入n,打印出s的搜有可能的值出现的概率
思路:
n个骰子的点数和的最小值为n,最大值为6n,且n个骰子的所有点数的排列数为6^n——需要先统计出每个点数出现的次数,然后把每个点数出现的次数除以6^n,即每个点数出现的概率
可用两种方法:递归和循环
递归的思路:
想求出n个骰子的点数和,可以先把n个骰子分为两堆:第一堆只有一个;另一堆有n-1个。单独的那堆有6种可能出现的点数,我们需要计算1~6的每一种点数和剩下的n-1个骰子来计算点数和
把n-1个骰子仍然分为两堆……
这是个递归的思路,递归结束的条件是最后只剩下一个骰子
定义一个6n-n+1的数组,将和为s的点数出现的次数保存到数组的第s-n个元素中
(相当于n个for循环,在最后一层for循环的时候把当前的n个固定值加起来,在probList的对应位置+1)
但是基于递归的实现有很多计算是重复的,因此当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个数字之和
(如在上一轮和为20的次数为x,在新的一轮中,加入了一个新的骰子,新的骰子的取值可能为1~6且都只有1次,因此在计算和为20的次数时,需要计算当新骰子为1时,之前为20-1的次数+新骰子为2时,之前为20-2的次数+……+新骰子为6,之前为20-6的次数,即第n-1、n-2、、n-3、n-4、n-5、n-6个数字之和)
代码:
// 递归
public class Q60_1 {
public static void main(String[] args) {
int n = 5;
printSum(n);
}
public static void printSum(int n) {
int maxV = 6;
if(n<1) {
return ;
}
// 初始化数组
int maxSum = maxV * n;
int[] probList = new int[maxSum-n+1];
// 递归计算骰子点数
Probability(n,probList);
// 计算基数
int total = (int)Math.pow(maxV, n);
// 打印比例
for(int i=n;i<maxSum;i++) {
System.out.printf("%d:%f\n",i,(double)probList[i-n]/total);
}
}
// 分成两堆,循环是指当分为单独一个的骰子分别取1~6时,另外的一堆的各种取值
public static void Probability(int n,int[] probList) {
// 第一轮递归时,没有需要记录的sum,初始sum即为单独的骰子被设置为的数值
for(int i=1;i<=6;i++) {
Probability(n,n,i,probList);
}
}
// sum为当前面的固定的和
public static void Probability(int orig, int curr, int sum, int[] probList) {
if(curr==1) {
probList[sum-orig]++;
}else {
for(int i=1;i<=6;i++) {
Probability(orig, curr-1, i+sum, probList);
}
}
}
}
// 循环
public class Q60_2 {
static int maxV = 6;
public static void main(String[] args) {
int n =3;
printSum(n);
}
public static void printSum(int n) {
if(n<1) {
return;
}
int[][] prob = new int[2][maxV*n+1];
int flag = 0;
// 第一轮的初始化
for(int i=1;i<=maxV;i++) {
prob[flag][i] = 1;
}
// k是加入了几颗骰子
for(int k=2;k<=n;k++) {
System.out.printf("%d time\n",k);
for(int i=1;i<=k;i++) {
prob[1-flag][i]=0;
}
// i是当前更新的位置
for(int i=k;i<=maxV*k;i++) {
prob[1-flag][i]=0;
// j是当前位置(和)的减去几 如n-1 n-2 n-3 n-4 n-5 n-6中的1~6
for(int j=1;j<=i && j<=maxV;j++) {
if(i-j<=maxV*(k-1)) { // 确认所加的数是上一轮的
prob[1-flag][i] += prob[flag][i-j];
}
}
}
flag = 1-flag;
}
double sum=0;
int total = (int)Math.pow(maxV, n);
for(int i=n;i<prob[flag].length;i++) {
sum+=(double)prob[flag][i]/total;
System.out.printf("%d:%f\n",i,(double)prob[flag][i]/total);
}
System.out.println(sum);
}
}