劍指offer 面試題43—n個骰子的點數
阿新 • • 發佈:2019-02-16
題目:
把n個骰子扔在地上,所有骰子朝上一面的點數之和為S。輸入n,打印出S的所有可能的值出現的概率。
解法一:遞迴
玩過麻將的都知道,骰子一共6個面,每個面上都有一個點數,對應的數字是1到 6之間的一個數字。所以,n個骰子的點數和的最小值為n,最大值為6n。因此,一個直觀的思路就是定義一個長度為6n-n的陣列,和為S的點數出現的次數儲存到陣列第S-n個元素裡。另外,我們還知道n個骰子的所有點數的排列數6^n。一旦我們統計出每一點數出現的次數之後,因此只要把每一點數出現的次數除以n^6,就得到了對應的概率。
該思路的關鍵就是統計每一點數出現的次數。要求出n個骰子的點數和,我們可以先把n 個骰子分為兩堆:第一堆只有一個,另一個有n-1個。單獨的那一個有可能出現從1到6的點數。我們需要計算從1到6的每一種點數和剩下的n-1個骰子來計算點數和。接下來把剩下的n-1個骰子還是分成兩堆,第一堆只有一個,第二堆有n-2個。我們把上一輪那個單獨骰子的點數和這一輪單獨骰子的點數相加,再和剩下的n-2個骰子來計算點數和。分析到這裡,我們不難發現,這是一種遞迴的思路。遞迴結束的條件就是最後只剩下一個骰子了。
int g_maxValue = 6; void PrintSumProbabilityOfDices_1(int number) { if(number < 1) return; int maxSum = number * g_maxValue; int* pProbabilities = new int[maxSum - number + 1]; for(int i = number; i <= maxSum; ++i) pProbabilities[i - number] = 0; SumProbabilityOfDices(number, pProbabilities); int total = pow((float)g_maxValue, number); for(int i = number; i <= maxSum; ++i) { float ratio = (float)pProbabilities[i - number] / total; printf("%d: %f\n", i, ratio); } delete[] pProbabilities; } void SumProbabilityOfDices(int number, int* pProbabilities) { for(int i = 1; i <= g_maxValue; ++i) SumProbabilityOfDices(number, number, i, 0, pProbabilities); } void SumProbabilityOfDices(int original, int current, int value, int tempSum, int* pProbabilities) { if(current == 1) { int sum = value + tempSum; pProbabilities[sum - original]++; } else { for(int i = 1; i <= g_maxValue; ++i) { int sum = value + tempSum; SumProbabilityOfDices(original, current - 1, i, sum, pProbabilities); } } }
解法二:雙陣列
上述演算法當number比較小的時候表現很優異。但由於該演算法基於遞迴,它有很多計算是重複的,從而導致當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之和。
void PrintSumProbabilityOfDices_2(int number)
{
if(number < 1) return;
double* pProbabilities[2];
pProbabilities[0] = new double[g_maxValue * number + 1];
pProbabilities[1] = new double[g_maxValue * number + 1];
for(int i = 0; i < g_maxValue * number + 1; ++i)
{
pProbabilities[0][i] = 0;
pProbabilities[1][i] = 0;
}
int flag = 0;
for (int i = 1; i <= g_maxValue; ++i)
pProbabilities[flag][i] = 1;
for (int k = 2; k <= number; ++k)
{
for (int i = k; i <= g_maxValue * k; ++i)
{
pProbabilities[1 - flag][i] = 0;
for(int j = 1; j <= i && j <= g_maxValue; ++j)
pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
}
flag = 1 - flag;
}
double total = pow((double)g_maxValue, number);
for(int i = number; i <= g_maxValue * number; ++i)
{
double ratio = pProbabilities[flag][i] / total;
printf("%d: %f\n", i, ratio);
}
delete[] pProbabilities[0];
delete[] pProbabilities[1];
}
值得提出來的是,上述程式碼沒有在函式裡把一個骰子的最大點數硬編碼(hard code)為6,而是用一個變數g_maxValue來表示。這樣做的好處時,如果某個廠家生產了最大點數為4或者8的骰子,我們只需要在程式碼中修改一個地方,擴充套件起來很方便。如果在面試的時候我們能對面試官提起對程式擴充套件性的考慮,一定能給面試官留下一個很好的印象。