《劍指offer》:[43]N個骰子的點數
阿新 • • 發佈:2019-02-15
題目:把N個骰子扔在地上,所有骰子朝上一面的點數之和為S,輸入N,打印出S的所有可能的值出現的概率。
分析:對於6點的N個骰子來講,其和S的最小值為N,最大值為6N。要得到和S出現的概率,就得得到和S出現的次數,然後用某一S出現的次數/S的所有可能性=S出現的概率。對於骰子進行全排列,我們很容易知道S的所有可能性為:6^N。那麼問題來了,怎麼計算出S出現的次數呢?
方案一:遞迴法。由於不能一口氣吃一個大胖子,所以我們必須把事情一步步的解決。該方法的思路是將N個骰子分成兩部分,第一部分是一個骰子,另一部分是剩下的N-1個。我們先計算第一部分第一個骰子出現的點數,很明顯,有1-6這6種可能性;然後再計算這一已知的部分和剩下的N-1個骰子出現的點數之和。仿照前例,我們可以將第二部分分解為第一部分(1個)和第二部分(剩下的N-2)。我們把上一輪和這裡的第一部分的和相加,得到新的點數之和,再將新的點數之和和剩下的N-2部分進行加法計算出新的點數之和。這就是典型的遞迴思想。結束條件是隻剩下最後一個骰子。
方案二:迴圈法。 該方法的主要思路是用兩個陣列來求的骰子值可能出現的次數,是一種空間換時間的做法。關鍵思想是:在一次迴圈中,第一個陣列中的第N個數字表示骰子和為N出現的次數。下一次迴圈中,我們加上一個新的骰子,此時和為S的骰子出現的次數應該等於第一個陣列中S-1,S-2,S-3,S-4,S-5,S-6的次數之和。這樣用一個數組來記錄上一步的結果,而下一步的結果要用到上一步的結果。該方法避免了複雜多餘的計算。
具體實現程式碼如下:
所以下面採用一種迴圈的方式來解決此問題,也是時間換空間的方法。
方案二程式碼實現:
分析:對於6點的N個骰子來講,其和S的最小值為N,最大值為6N。要得到和S出現的概率,就得得到和S出現的次數,然後用某一S出現的次數/S的所有可能性=S出現的概率。對於骰子進行全排列,我們很容易知道S的所有可能性為:6^N。那麼問題來了,怎麼計算出S出現的次數呢?
方案一:遞迴法。由於不能一口氣吃一個大胖子,所以我們必須把事情一步步的解決。該方法的思路是將N個骰子分成兩部分,第一部分是一個骰子,另一部分是剩下的N-1個。我們先計算第一部分第一個骰子出現的點數,很明顯,有1-6這6種可能性;然後再計算這一已知的部分和剩下的N-1個骰子出現的點數之和。仿照前例,我們可以將第二部分分解為第一部分(1個)和第二部分(剩下的N-2)。我們把上一輪和這裡的第一部分的和相加,得到新的點數之和,再將新的點數之和和剩下的N-2部分進行加法計算出新的點數之和。這就是典型的遞迴思想。結束條件是隻剩下最後一個骰子。
方案二:迴圈法。
具體實現程式碼如下:
#include <iostream> #include <math.h> #include <stdlib.h> using namespace std; int MaxValue=6; void Probabilityhelp(int original,int current,int sum,int *probabilitys) { if(current==1)//當只剩下最後一個骰子的時候,計算其和; { probabilitys[sum-original]++;//和為sum的陣列+1; } else { for(int i=1;i<=MaxValue;i++) { Probabilityhelp(original,current-1,sum+i,probabilitys);//剩下的繼續遞迴; } } } void Probability(int number,int *pProbability) { for(int i=1;i<=MaxValue;i++) Probabilityhelp(number,number,i,pProbability); } void PrintProbability(int number) { if(number<1) return; int MaxSum=number*MaxValue; int *pProbability=new int[MaxSum-number+1];//我們定義一個6N-N+1的陣列,和為S的點數出現的次數儲存到陣列第S-N個元素裡。 for(int i=number;i<=MaxSum;i++) pProbability[i-number]=0;//次數都初始化為0次; Probability(number,pProbability); int total=pow((double)MaxValue,number);//總共出現的值的可能性; for(int i=number;i<=MaxSum;i++) { double ratio=(double)pProbability[i-number]/total;//出現的次數/總的可能性; cout<<i<<" "<<ratio<<endl; } int a=1; int b=2; delete []pProbability; } int main() { PrintProbability(6); system("pause"); return 0; }
執行結果:
所以下面採用一種迴圈的方式來解決此問題,也是時間換空間的方法。
方案二程式碼實現:
#include <iostream> #include <math.h> using namespace std; int g_maxValue=6; void PrintProbability(int n) { if(n<1) return; int* pProbability[2]; pProbability[0]=new int[g_maxValue*n+1]; pProbability[1]=new int[g_maxValue*n+1]; for(int i=0;i<=g_maxValue*n;i++) { pProbability[0][i]=0; pProbability[1][i]=0; } int flag=0; for(int i=1;i<=g_maxValue;i++) pProbability[flag][i]=1; for(int k=2;k<=n;k++) { for(int i=0;i<k;i++) pProbability[1-flag][i]=0; for(int i=k;i<=g_maxValue*k;i++) { pProbability[1-flag][i]=0; for(int j=1;j<=i && j<=g_maxValue;j++) pProbability[1-flag][i]+=pProbability[flag][i-j];//這一步就是求和為S時的次數為上一步S-1,S-2,S-3,S-4,S-5,S-6的總次數; } flag=1-flag; } int total=pow((double)g_maxValue,n); double prob=0; for(int i=n;i<=g_maxValue*n;i++) { double ratio=(double)pProbability[flag][i]/total; prob+=ratio; cout<<i<<" "<<ratio<<" "<<endl; } cout<<"校驗和是否1:"<<prob<<endl; cout<<endl; delete[] pProbability[0]; delete[] pProbability[1]; } int main() { PrintProbability(2); system("pause"); return 0; }
執行結果:
此種方法的時間效率相對於方案有所提高,但是藉助了輔助空間。