1. 程式人生 > >面試題43: n個骰子的點數

面試題43: n個骰子的點數

原題:把n個骰子扔到地上,所有骰子朝上一面的點數之後為s. 輸入n,打印出s所有可能的值出現的概率。(每個骰子6個面,點數從1到6)

解法一:基於遞迴,時間效率不高

遞迴的思想一般是分而治之,把n個骰子分為第一個和剩下的n-1個。先計算第一個骰子每個點數出現的次數,再計算剩餘n-1個骰子出現的點數之和。求n-1個骰子的點數之的方法和前面講的一樣,即再次把n-1個骰子分成兩堆------第一個和剩下的n-2個。n個骰子,每個骰子6個面,總共有6n個組合。這6n個組合之中肯定有重複的,我們知道其範圍是n~6n,對於每種情況我們可以用快取機制記錄下來,每當其發生一次我們令其對應的單元加1。

我們定義一個長度為6n-n+1的陣列,和為s的點數出現的次數儲存到陣列第s-n個元素裡。為什麼是6n-n+1呢?因為n個骰子的和最少是n,最大是6n,介於這兩者之間的每一個情況都可能會發生,總共6n-n+1種情況。

      private static final int g_maxValue = 6;
      //基於遞迴求骰子點數,時間效率不高
      public static void PrintProbability(int number){
          if(number<1) return;
          int maxSum = number*g_maxValue;
          int[] pProbabilities = new int[maxSum-number+1];
          //初始化,開始統計之前都為0次
          for(int i=number;i<=maxSum;i++){
              pProbabilities[i-number] = 0;
         }
         double total = Math.pow(g_maxValue,number);
         //probability(number,pProbabilities);這個函式計算n~6n每種情況出現的次數
         probability(number,pProbabilities);
         for(int i=number;i<=maxSum;i++){
             double ratio = pProbabilities[i-number]/total;
             System.out.println("i: "+i+" ratio: "+ratio);
        }
     }
     public static void probability(int number,int[] pProbabilities){
        for(int i=1;i<=g_maxValue;i++){//從第一個骰子開始
             probability(number,number,i,pProbabilities);
         }
    }
     //總共original個骰子,當前第 current個骰子,當前的和,貫穿始終的陣列
     public static void probability(int original,int current,int sum,int[] pProbabilities){
       if(current==1){
             pProbabilities[sum-original]++;
         }else{
             for(int i=1;i<=g_maxValue;i++){
                probability(original,current-1,sum+i,pProbabilities);
             }
        }
     }
考慮n=2時的遞迴過程,首先nT=2,nC=2,sum=1,表明第一個骰子甩出一個1,由於nC=2表明現在有兩個骰子,所以進入else部分,i又從1到6迴圈,表明這是進入到第二個骰子在甩了,首先i為1,表明又甩出一個1,這時候nC=1,就將2-n的位置上加1,表明結果為2的次數加1,然後退到上一層,i++,此時還是第二個骰子在甩,甩出一個2,此時sum=3,nC=1,所以在和為3的位置上加1,一直這樣,到了和為7的位置上加1的時候,會退到在上一次迴圈,這時候表明第一個骰子甩出了一個2,此時進入第二個骰子,依次會出現和為3,4,5,6,7,8的結果,然後再在相應位置上加1即可

解法二:基於迴圈,時間效能好

遞迴一般是自頂向下的分析求解,而基於迴圈的方法則是自底向上。基於迴圈的一般需要更少的空間和更少的時間,效能較好,但是一般程式碼比較難懂。

      //基於迴圈求骰子點數
      public static void PrintProbability_1(int number){
          if(number<1){
              return;
          }
          int[][] pProbabilities = new int[2][g_maxValue*number +1];
          for(int i=0;i<g_maxValue;i++){//初始化陣列
               pProbabilities[0][i] = 0;
               pProbabilities[1][i] = 0;
         }
         int flag = 0;
         for(int i=1;i<=g_maxValue;i++){//當第一次拋擲骰子時,有6種可能,每種可能出現一次
             pProbabilities[flag][i] = 1;//我們以probabilities[0]作為初始的陣列,那麼我們對這個陣列進行初始化是要將1-6都賦值為1,說明第一個骰子投完的結果存到了probabilities[0]
         }
         //從第二次開始擲骰子,假設第一個陣列中的第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之和
         for(int k =2;k<=number;k++){
             for(int i=0;i<k;i++){//第k次擲骰子,和最小為k,小於k的情況是不可能發生的!所以另不可能發生的次數設定為0!
                 pProbabilities[1-flag][i] = 0;
             }
             for(int i=k;i<=g_maxValue*k;i++){//第k次擲骰子,和最小為k,最大為g_maxValue*k
                 pProbabilities[1-flag][i] = 0;//初始化,因為這個陣列要重複使用,上一次的值要清0
                 for(int j=1;j<=i&&j<=g_maxValue;j++){
                     pProbabilities[1-flag][i] += pProbabilities[flag][i-j];
                 }
             }
             flag = 1-flag;
         }
         double total = Math.pow(g_maxValue, number);
         for(int i=number;i<=g_maxValue*number;i++){
             double ratio = pProbabilities[flag][i]/total;
             System.out.println("sum: "+i+" ratio: "+ratio);
         }
     }