本文參考自《劍指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個骰子的每個點數和出現的次數。
