1. 程式人生 > >劍指offer 面試題43—n個骰子的點數

劍指offer 面試題43—n個骰子的點數

題目:

n個骰子扔在地上,所有骰子朝上一面的點數之和為S。輸入n,打印出S的所有可能的值出現的概率。

解法一:遞迴

       玩過麻將的都知道,骰子一共6個面,每個面上都有一個點數,對應的數字是1 6之間的一個數字。所以,n個骰子的點數和的最小值為n,最大值為6n。因此,一個直觀的思路就是定義一個長度為6n-n的陣列,和為S的點數出現的次數儲存到陣列第S-n個元素裡。另外,我們還知道n個骰子的所有點數的排列數6^n。一旦我們統計出每一點數出現的次數之後,因此只要把每一點數出現的次數除以n^6,就得到了對應的概率。

       該思路的關鍵就是統計每一點數出現的次數。要求出n個骰子的點數和,我們可以先把n

個骰子分為兩堆:第一堆只有一個,另一個有n-1個。單獨的那一個有可能出現從16的點數。我們需要計算從16的每一種點數和剩下的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-1n-2n-3n-4n-5n-6的總和。所以我們把另一個數組的第n個數字設為前一個數組對應的第n-1n-2n-3n-4n-5n-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的骰子,我們只需要在程式碼中修改一個地方,擴充套件起來很方便。如果在面試的時候我們能對面試官提起對程式擴充套件性的考慮,一定能給面試官留下一個很好的印象。