題目:java
扔 n 個骰子,向上面的數字之和爲 S。給定 Given n,請列出全部可能的 S值及其相應的機率。算法
給定
n = 1
,返回[ [1, 0.17], [2, 0.17], [3, 0.17], [4, 0.17], [5, 0.17], [6, 0.17]]
。數組
方法一spa
機率問題最簡單有效的方法固然是枚舉啊,何況如今的計算機這麼優秀。code
n個骰子一塊兒投擲,先列出全部可能的結果,而後求和,計數,最後算機率;blog
首先枚舉須要的空間先給上事件
int[][] matrix = new int[(int) Math.pow(6, n)][n];
而後進行n重循環,枚舉全部的可能狀況get
int speed = 0; // 變化的步長 int count = 0; // 計數器 int point = 0; // 當前要寫入的數值 for (int i = 0; i < n; i++) { speed = (int) Math.pow(6, i); count = 0; point = 0; for (int j = 0; j < MAX; j++, count++) { if (count == speed) { count = 0; point++; } matrix[j][i] = (int) (point % 6 + 1); } }
而後就得到了全部的狀況,並且這些狀況都是等機率的;數學
以後就就很容易了,it
然而。。。
運行到 n = 8 的時候崩了。。。
把 int變成 short,再改爲 char,都很差使,,,額,這個出題人不想讓咱們用枚舉。。。
是的後面當 n = 15 時,本地的IDE頁崩了;
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
因此這題要用到一些算法知識。。。
方法二
再此向你們介紹全概公式:
全機率公式爲機率論中的重要公式,它將對一復瑣事件A的機率求解問題轉化爲了在不一樣狀況下發生的簡單事件的機率的求和問題。內容:若是事件B一、B二、B3…Bn 構成一個完備事件組,即它們兩兩互不相容,其和爲全集;而且P(Bi)大於0,則對任一事件A有P(A)=P(A|B1)P(B1) + P(A|B2)P(B2) + ... + P(A|Bn)P(Bn)。或者:p(A)=P(AB1)+P(AB2)+...+P(ABn)),其中A與Bn的關係爲交)。
這個題目正好就是這樣
投 6個骰子點數和爲 25的機率 = 投 6個骰子點數和爲 25而且最後一個點數爲1的機率 + 投 6個骰子點數和爲 25而且最後一個點數爲2的機率
+ 投 6個骰子點數和爲 25而且最後一個點數爲3的機率 + ... + 投 6個骰子點數和爲 25而且最後一個點數爲6的機率;
換言之:
投 6個骰子點數和爲 25的機率 = 投 前5個骰子點數和爲 24而且最後一個點數爲1的機率 + 投 前5個骰子點數和爲 23而且最後一個點數爲2的機率
+ 投 前5個骰子點數和爲 22而且最後一個點數爲3的機率 + ... + 投 前5個骰子點數和爲 19而且最後一個點數爲6的機率;
得出廣泛結論:
投 n個骰子點數和爲 Sum的機率 = 投 前 n-1個骰子點數和爲 Sum-1而且最後一個點數爲1的機率 + 投 前 n-1個骰子點數和爲 Sum-2而且最後一個點數爲2的機率
+ 投 前 n-1個骰子點數和爲 Sum-3而且最後一個點數爲3的機率 + ... + 投 前 n-1個骰子點數和爲 Sum-6而且最後一個點數爲6的機率;
舉例檢驗:
投 2個骰子點數和爲 6的機率 = 投 第一個骰子點數爲 5而且第二個點數爲1的機率(1/6 * 1/6) + 投 第一個骰子點數爲 4而且第二個點數爲2的機率(1/6 * 1/6)
+ 投 第一個骰子點數爲 3而且第二個點數爲3的機率(1/6 * 1/6) + 投 第一個骰子點數爲 2而且第二個點數爲4的機率(1/6 * 1/6)
+ 投 第一個骰子點數爲 1而且第二個點數爲5的機率(1/6 * 1/6) + 投 第一個骰子點數爲 0而且第二個點數爲6的機率(0/6 * 1/6) = 5/36;
投 2次的機率能夠從投 1次的機率中得出,投 3次的機率能夠從投 2次的機率中得出,投 4次的機率能夠從投 3次的機率中得出...
因此咱們能夠從第二次一直計算到第 n次,
因爲機率的分母爲全部可能出現的狀況的總數爲定值:爲 6的n次方;
因此咱們能夠先只記錄可能種類的次數,最後再算機率;
double[][] matrix_II = new double[100][1000]; for (int i = 1; i < matrix_II.length; i++) { for (int j = 0; j < matrix_II[0].length; j++) { matrix_II[i][j] = 0; } } matrix_II[1][1] = matrix_II[1][2] = matrix_II[1][3] = 1; matrix_II[1][4] = matrix_II[1][5] = matrix_II[1][6] = 1; for (int i = 2; i <= n; i++) { for (int j = i; j <= 6 * i; j++) { double sum2 = 0; for (int k = j - 6; k <= j - 1; k++) { if (k > 0) { sum2 += matrix_II[i - 1][k]; } } matrix_II[i][j] = sum2; } }
格式化後,
第一層循環:從第二次到第 n次;
第二層循環:n次投擲可能的結果:n到 6n;
第三層循環:本次的機率與上次的 6種情形的機率有關;
循環完畢即獲得了一個從1到 n次的投擲狀況的次數矩陣;
而後只要在最後遍歷一次最後一趟做爲輸出就好了;
附上程序:
public class Solution { /** * @param n an integer * @return a list of Map.Entry<sum, probability> */ public List<Map.Entry<Integer, Double>> dicesSum(int n) { // Write your code here // Ps. new AbstractMap.SimpleEntry<Integer, Double>(sum, pro) // to create the pair double MAX = Math.pow(6, n); double[][] matrix_II = new double[100][1000]; for (int i = 1; i < matrix_II.length; i++) { for (int j = 0; j < matrix_II[0].length; j++) { matrix_II[i][j] = 0; } } matrix_II[1][1] = matrix_II[1][2] = matrix_II[1][3] = 1; matrix_II[1][4] = matrix_II[1][5] = matrix_II[1][6] = 1; double sum2 = 0; for (int i = 2; i <= n; i++) { for (int j = i; j <= 6 * i; j++) { sum2 = 0; for (int k = j - 6; k <= j - 1; k++) { if (k >= i - 1) { sum2 += matrix_II[i - 1][k]; } } matrix_II[i][j] = sum2; } } List<Map.Entry<Integer, Double>> list = new ArrayList<>(); for (int i = n; i <= 6 * n; i++) { AbstractMap.SimpleEntry<Integer, Double> entry = new AbstractMap.SimpleEntry<>(i, matrix_II[n][i] / MAX); list.add(entry); } return list; } }
須要注意的是當程序運行到 n=15 時,數值已經大過 Int類型了;
喲。
進階 方法三
上面的方法二應該是最有效的方法了,但彷佛感受仍是有點不讓人滿意,求 n的機率必須把前面的機率都算一遍;
有沒有一種直接就去找 n的算法呢?
有啊,由於咱們平時的思考方式確定不是從 1開始推啊;
常見的問題:投擲 3個骰子向上的點數和爲 7的機率爲多少?
確定是推:有115(3),124(6),133(3),223(3),3+6+3+3=15,機率爲15/36 = 5/12;
咱們也能夠這樣啊,分別直接去求 n到 6n的機率;
1.獲取去重全排列的個數;
2.獲取關鍵的數組(如115,124等);
首先是簡單的獲取第一個關鍵數組,即和爲 sum,各個位置的數值從左往右遞增;
int[] arr = new int[n]; for (int i = 0; i < n - 1; i++) { arr[i] = 1; } arr[n - 1] = sum - n + 1; for (int i = 1; i < n; i++) { if (arr[n - i] > 6) { arr[n - i - 1] = arr[n - i - 1] + arr[n - i] - 6; arr[n - i] = 6; } }
從倒數第二位開始,進行判斷,替換和從新構造;
哈哈,以後就不會寫了。。。
for (int i = n - 2; i >= 0; i--) { // 從倒數第二個開始 while (arr[i] + 1 < 6 && arr[i + 1] - 1 > 0 && arr[i + 1] - arr[i] >= 2) { arr[i] += 1; arr[i + 1] -= 1; for (int index = 0; index < n; index++) { System.out.print(arr[index]); } System.out.println(); } }
大體是這麼個結構。。。但還少進位,重構等功能;
我在這裏就陣亡了,諸位算法大師,數學家,就拜託大家了;
重置 方法一
誰說枚舉不能用了。。。只要不佔用那麼多的空間不就行了;
上程序
public class Solution { /** * @param n an integer * @return a list of Map.Entry<sum, probability> */ public List<Map.Entry<Integer, Double>> dicesSum(int n) { // Write your code here // Ps. new AbstractMap.SimpleEntry<Integer, Double>(sum, pro) // to create the pair double MAX = Math.pow(6, n); double[] sum_array = new double[6 * n + 1]; for (int i = 0; i < sum_array.length; i++) { sum_array[i] = 0; } int[]matrix = new int[n+1]; for (int i = 1; i < matrix.length; i++) { matrix[i] = 1; } int sum; while (true) { sum = 0; for (int b = 1; b <= n; b++) { sum += matrix[b]; } sum_array[sum]++; matrix[n]++; for (int i = n; i > 0; i--) { if(matrix[i] == 7) { matrix[i-1]++; matrix[i] = 1; } } if(matrix[0] > 0) { break; } } List<Map.Entry<Integer, Double>> list = new ArrayList<>(); for (int i = n; i <= 6 * n; i++) { AbstractMap.SimpleEntry<Integer, Double> entry = new AbstractMap.SimpleEntry<>(i, sum_array[i] / MAX); list.add(entry); } return list; } }
就想到了。。。枚舉超時了。。。
行了,不掙扎了。