题目:
把n个骰子扔在地上,所有骰子朝上的一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
分析:
n个骰子点数和最小值为n,最大值为6n。根据排列组合,n个骰子的所有点数的排列数为,需要先统计每个点数出现的次数,再把次数除以,就能求出每个点数出现的概率。
基于对个求骰子点数,时间效率不够高
先把n个骰子分成两堆:第一堆有1个,第二堆有n-1个。第一堆可能出现1~6的情况,我们需要计算1~6的每一种点数和剩下的n-1个骰子的点数和。接下来把剩下的n-1个骰子仍然分成两堆:第一堆有1个,第二堆有n-2个。把上一轮单独的骰子和这一轮单独的骰子点数相加,再和剩下的n-2个骰子来计算点数和。这是一个递归的思路,递归终止的 条件是最后剩下了一个骰子。
定义一个6n-n+1长度的数组,将和为s的点数出现的次数放到下标为s-n的位置。
基于循环求骰子点数,时间性能好
递归中存在大量的重复计算,理解起来也有难度,我没看懂,我们使用循环的方式解决这个问题。
设f(n,k)表示n个骰子,某次骰子之和为k的次数,要想让n个骰子的结果为k,这个结果和n-1个骰子的结果有联系,那么有6种情况,第n个骰子可能出现1~6的情况,那么前n-1个骰子就要分别出现k-1~k-6的情况,于是f(n,k)=f(n-1,k-1)+f(n-1,k-2)+f(n-1,k-3)+f(n-1,k-4)+f(n-1,k-5)+f(n-1,k-6),这里的n>0,k≤6n。最开始的情况是什么样呢?f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1。使用递归的思路分析,使用循环的方式,自底向上实现代码求解。
解法:
基于对个求骰子点数,时间效率不够高
package com.wsy;
public class Main {
public static int[] result;
public static int maxValue = 6;
public static void main(String[] args) {
int n = 5;
printProbability(n);
}
public static void printProbability(int n) {
if (n < 1) {
return;
}
result = new int[n * maxValue - n + 1];
int maxSum = maxValue * n;
for (int i = 1; i <= maxValue; i++) {
probability(n, n, i);
}
int total = (int) Math.pow(maxValue, n);
for (int i = n; i <= maxSum; i++) {
System.out.println("s=" + i + "\tp=" + (1.0 * result[i - n] / total));
}
}
public static void probability(int original, int current, int sum) {
if (current == 1) {
result[sum - original]++;
} else {
for (int i = 1; i <= maxValue; i++) {
probability(original, current - 1, i + sum);
}
}
}
}
基于循环求骰子点数,时间性能好
package com.wsy;
public class Main {
public static int[] result;
public static int maxValue = 6;
public static void main(String[] args) {
int n = 6;
printProbability(n);
}
public static void printProbability(int n) {
if (n < 1) {
return;
}
result = new int[maxValue * n + 1];
// 投出第1个骰子
for (int i = 1; i <= maxValue; i++) {// f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1
result[i] = 1;
}
for (int i = 2; i <= n; i++) {// 依次投出第i个骰子
// 从后面向前更新result,因为投出第i个骰子后,[i,i*maxValue]之间的值都要更新,新增了一个骰子,可能构成的情况更多了
// [1,i]之间的值也要更新,增加了骰子,有的值就取不到了,比如n个骰子,sum<n的情况
// 后面的值要用到前面的值在n-1时候的状态,如果先更新前面的值,后面的值在计算的时候,前面的值就是n时候的状态了
for (int j = i * maxValue; j >= 1; j--) {
int temp = 0;
// 计算f(n-1,j-1)+f(n-1,j-2)+f(n-1,j-3)+f(n-1,j-4)+f(n-1,j-5)+f(n-1,j-6),注意有的时候j小于6,累加不到j-6,就用0代替
for (int k = 1; k <= maxValue; k++) {
temp += j > k ? result[j - k] : 0;
}
result[j] = temp;
}
}
int total = (int) Math.pow(maxValue, n);
for (int i = n; i <= n * maxValue; i++) {
System.out.println("s=" + i + "\tp=" + (1.0 * result[i] / total));
}
}
}