LeetCode打卡day7

136 阅读2分钟

“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个筛子得出,故递推公式

image.png

很明显对于2个筛子不可能有1这样的点数组合,对于n个筛子的取值范围为n~6n长度为5n+1。因为对于不同筛子数量取值范围都有区别,若是正向推导我们要判断不同筛子数量的x-i是否越界问题。这无疑增加解题难度

故我们反向推导,对于n-1个筛子其取值集合加上1~6都不会产生越界判断,因为n个筛子取值集合就是这样产生的。故我们遍历dp(n-1)对于

dp(n,x+i)=i=16dp(n1,x)16dp(n,x+i)=\sum_{i=1}^6 dp(n-1,x)*{1\over 6}

最后我们要解决最后一个难点,我们编写程序时思路上的数字和对应的序号是有差异的。例如对于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]
};

四、总结

难点主要是动态规划的递归公式的推导,以及为了简易实现省去复杂的正向边界判断,我们反向实现递推公式。