劍指 Offer 60. n個骰子的點數

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

你需要用一個浮點數陣列返回答案,其中第 i 個元素代表這 n 個骰子所能擲出的點數集合中第 i 小的那個的概率。

示例 1:

輸入: 1
輸出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

示例 2:

輸入: 2
輸出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

限制:

  • 1 <= n <= 11

做題思路:

說實話,這道題是看了k神(https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/jian-zhi-offer-60-n-ge-tou-zi-de-dian-sh-z36d/)的程式碼和題解才知道怎麼做的,先放k神程式碼吧。

class Solution {
public double[] dicesProbability(int n) {
double[] dp = new double[6];
Arrays.fill(dp, 1.0 / 6.0);
for (int i = 2; i <= n; i++) {
double[] tmp = new double[5 * i + 1];
for (int j = 0; j < dp.length; j++) {
for (int k = 0; k < 6; k++) {
tmp[j + k] += dp[j] / 6.0;
}
}
dp = tmp;
}
return dp;
}
}

這是根據K神程式碼自己加了些註釋,希望其他人可以看懂。

class Solution {
public double[] dicesProbability(int n) {
double[] dp = new double[6];
//只有一個骰子時,陣列初始化
Arrays.fill(dp, 1.0/6.0);
//骰子的個數進行逐次累加,骰子總數從2開始
for(int i = 2 ;i <= n;i++){
//新陣列存放骰子的每個總數和出現的概率
//骰子總數為i時,骰子可能出現的點數i~6i,長度6i-i+1
double[] tmp = new double[5*i+1];
//骰子數為i-1時,所有骰子總數和出現的情況,j值表示陣列中的索引,並不表示骰子總數和的數值。
for(int j = 0; j < dp.length; j++){
//新加骰子可能出現的點數1~6,在陣列中影響前面索引加0~5的索引指向的值
for(int k = 0;k < 6; k++){
//正向遞推,骰子總數為i-1的陣列出現的每一個點數和,
//單獨拿出來依次和新點數(1~6)相加,計算相加後點數和的概率
tmp[j + k] += dp[j]/6;
}
}
//骰子總數為i的陣列賦值骰子總數為i-1的陣列
dp = tmp;
}
return dp;
}
}

這位力友大佬力扣 (leetcode-cn.com)的思路更詳細點,分享出來,希望有用。

解釋思路:

這道題的正向推導其實用概率獨立性去解釋可能更好理解。
假設有兩個骰子A、B,這兩個骰子相互獨立。
在僅有一個骰子A的情況下,6個點出現的概率都為1/6, 同時A的每個點搭配B的每個點的概率也是相同的,所以骰子A為1會分別乘6個1/6去搭配骰子B的1~6, 即這種搭配2~7的概率分別都為1/36; 骰子A的2也會分別去乘6個1/6去搭配骰子B的1~6, 即這種搭配3~8的概率也分別為1/36, 以此類推。
因為這是獨立重複試驗,所以之間的關係是相加,所有實驗相加後,就得到了最終的結果。
其實本質是高中數學,但是由於加了場景並且要程式碼實現,所以變難了。

程式碼:

class Solution {
public double[] dicesProbability(int n) {
//因為最後的結果只與前一個動態轉移陣列有關,所以這裡只需要設定一個一維的動態轉移陣列
//原本dp[i][j]表示的是前i個骰子的點數之和為j的概率,現在只需要最後的狀態的陣列,所以就只用一個一維陣列dp[j]表示n個骰子下每個結果的概率。
//初始是1個骰子情況下的點數之和情況,就只有6個結果,所以用dp的初始化的size是6個
double[] dp = new double[6];
//只有一個數組
Arrays.fill(dp,1.0/6.0);
//從第2個骰子開始,這裡n表示n個骰子,先從第二個的情況算起,然後再逐步求3個、4個···n個的情況
//i表示當總共i個骰子時的結果
for(int i=2;i<=n;i++){
//每次的點數之和範圍會有點變化,點數之和的值最大是i*6,最小是i*1,i之前的結果值是不會出現的;
//比如i=3個骰子時,最小就是3了,不可能是2和1,所以點數之和的值的個數是6*i-(i-1),化簡:5*i+1
//當有i個骰子時的點數之和的值陣列先假定是temp
double[] temp = new double[5*i+1];
//從i-1個骰子的點數之和的值陣列入手,計算i個骰子的點數之和陣列的值
//先拿i-1個骰子的點數之和陣列的第j個值,它所影響的是i個骰子時的temp[j+k]的值
for(int j=0;j<dp.length;j++){
//比如只有1個骰子時,dp[1]是代表當骰子點數之和為2時的概率,它會對當有2個骰子時的點數之和為3、4、5、6、7、8產生影響,因為當有一個骰子的值為2時,另一個骰子的值可以為1~6,產生的點數之和相應的就是3~8;比如dp[2]代表點數之和為3,它會對有2個骰子時的點數之和為4、5、6、7、8、9產生影響;所以k在這裡就是對應著第i個骰子出現時可能出現六種情況,這裡可能畫一個K神那樣的動態規劃逆推的圖就好理解很多
for(int k=0;k<6;k++){
//這裡記得是加上dp陣列值與1/6的乘積,1/6是第i個骰子投出某個值的概率
temp[j+k]+=dp[j]*(1.0/6.0);
}
}
//i個骰子的點數之和全都算出來後,要將temp陣列移交給dp陣列,dp陣列就會代表i個骰子時的可能出現的點數之和的概率;用於計算i+1個骰子時的點數之和的概率
dp = temp;
}
return dp;
}
}