劍指Offer對答如流系列 - n個骰子的點數

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

一、題目描述

把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,即可結束遍歷。

可以感受到,計算量實在太大了,效率會比較低。

思路二:動態規劃

劍指Offer的解答過於繁瑣與複雜。這裏提供一種相對理解簡單,容易實現的方法。

我們通過分析能夠發現 n個骰子的點數依賴於n-1個骰子的點數,相當於在n-1個骰子點數的基礎上再進行投擲。

由此定義狀態轉移方程爲f(n,k)表示n個骰子點數和爲k時出現的次數,於是可得:

f(n,k)=f(n−1,k−1)+f(n−1,k−2)+f(n−1,k−3)+f(n−1,k−4)+f(n−1,k−5)+f(n−1,k−6)
其中 n>0且k<=6n,f(n−1,k−i)表示的是第n次擲骰子時,骰子的點數爲i對應的情況

從k−1到k−6分別對應第n次擲骰子時骰子正面爲1到6的情況。而初始狀態可以定義爲:

f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1

三、問題解答

思路一:

    private final int maxValue = 6;

    public  void printProbability1(int number) {
        if(number<=0) {
            return;
        }

        int[] probabilities = new int[maxValue*number-number+1];
        Arrays.fill(probabilities,0);

        //計算不同點數出現的次數
        for(int i=1;i<=maxValue;i++) {
            //第一次擲骰子,總點數只能是1~maxValue(即6)
            calP(probabilities, number, number-1, i);
        }
        
        //所有情況總共出現的次數
        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));
        }
    }

    private  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  void printProbability(int number) {
        double total = Math.pow(6,number);
        for(int i=number; i<=6*number; i++) {
            double ratio = (double)getNSumCount(number,i)/total;
            NumberFormat format = NumberFormat.getPercentInstance();
            format.setMaximumFractionDigits(2);//設置保留幾位小數
            System.out.println("點數和爲"+getNSumCount(number,i)+"的概率爲:"+format.format(ratio));
        }
    }

    private int getNSumCount(int n, int sum) {
        if(n<1 || sum<n || sum>6*n) {
            return 0;
        }
        if(n==1) {
            return 1;
        }
        int resCount;
        resCount = getNSumCount(n-1,sum-1)+getNSumCount(n-1,sum-2)+
                getNSumCount(n-1,sum-3)+getNSumCount(n-1,sum-4)+
                getNSumCount(n-1,sum-5)+getNSumCount(n-1,sum-6);
        return resCount;
    }
發佈了194 篇原創文章 · 獲贊 3472 · 訪問量 53萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章