“Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。”
一、题目描述:
剑指 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
二、思路分析:
n个筛子有6^n钟组合,若暴力破解明显超标。我们可以从暴力法中得到一个启示,n个筛子点数集合肯定和n-1个筛子点数集合有关。因此我们使用动态规划方法
假设我们已经知道n-1个筛子的点数集合的概率,对于n个筛子我们用dp(n,x)表示n个筛子掷出点数x的概率,我们可以推导出它们之间的关系,对于第n个筛子它只能有1~6的取值i,则剩下的x-i取值由n-1个筛子得出,故递推公式
很明显对于2个筛子不可能有1这样的点数组合,对于n个筛子的取值范围为n~6n长度为5n+1。因为对于不同筛子数量取值范围都有区别,若是正向推导我们要判断不同筛子数量的x-i是否越界问题。这无疑增加解题难度
故我们反向推导,对于n-1个筛子其取值集合加上1~6都不会产生越界判断,因为n个筛子取值集合就是这样产生的。故我们遍历dp(n-1)对于
最后我们要解决最后一个难点,我们编写程序时思路上的数字和对应的序号是有差异的。例如对于n=1,dp(1)数组dp[0][0]对应的是掷出1的概率,对于n=2 dp[1][0]对于的是掷出2的概率,故要实现公式需要调整参数dp[1][0+1-1]+=dp[0][0]
三、AC代码
/**
* @param {number} n
* @return {number[]}
*/
var dicesProbability = function(n) {
let dp=new Array(n+1)
dp[1]=new Array(6).fill(1/6)
for(let i=2;i<=n;i++){
dp[i]=new Array(5*i+1).fill(0)
for(let j=0;j<dp[i-1].length;j++){
for(let a=1;a<=6;a++){
dp[i][j+a-1]+=dp[i-1][j]*1/6
}
}
}
return dp[n]
};
四、总结
难点主要是动态规划的递归公式的推导,以及为了简易实现省去复杂的正向边界判断,我们反向实现递推公式。