剑指 Offer 60. n个骰子的点数


剑指 Offer 60. n个骰子的点数

解题思路

解题前须知
在这里插入图片描述
本题可用动态规划求解
1、我们先建立二维 dp 数组,dp[n][s] 表示投掷 n 个骰子,n 个朝上的面的点数之和为 s 的事件出现的次数。

2、那么动态转移方程就是:dp[n][s] += dp[n \- 1][s \- k],k 属于 [1, 6]

  • 我们可以举个例子来理解,假如 n = 3, s = 8,那么 dp[3][8] 表示投掷 3 个骰子,3 个朝上的面的点数之和为 8 的事件出现的次数。那么我们可以把求 dp[3][8] 转移为求只投掷 2 个骰子,2 个朝上的面的点数之和分别为 7、6、5、4、3、2 的事件的次数之和。
  • 因为假如 “只投掷 2 个骰子,2 个朝上的面的点数之和为 7”,那么我们只需要再投一个骰子,让它的点数是 1,不就满足了 dp[3][8] 了嘛!
  • 又比如 “只投掷 2 个骰子,2 个朝上的面的点数之和为 6”,那么我们只需要再投一个骰子,让它的点数是 2,不就满足了 dp[3][8] 了嘛!
  • 依次类推

3、当然,上面的动态转移方程的前提条件是要保证 s - k > 0,因为没有骰子能投掷出小于等于 0 的点数。

在这里插入图片描述

代码

class Solution {
    public double[] dicesProbability(int n) {
        if (n <= 0) {
            return new double[0];
        }
        /*
        首先为了方便表达(dp[0][0]是没有含义的),我们将 dp 数组的行的数量设为 n + 1,列的数量设为 6n + 1。
        行表示 n,列表示 s,且 s 的最大值是 6n。
         */
        //dp[n][s] 表示投掷 n 个骰子,n 个朝上的面的点数之和为 s 的事件出现的次数
        int[][] dp = new int[n + 1][6 * n + 1];

        /*
            初始化 一颗骰子的情况.
            一颗骰子, s 的每个值可能出现的次数都为 1
        */
        for (int i = 1; i <= 6; i++) {
            dp[1][i] = 1;
        }

        for (int i = 2; i <= n; i++) {//表示骰子的个数
            for (int s = i; s <= 6 * i; s++) {//表示可能会出现的点数之和
                for (int j = 1; j <= 6; j++) {//表示当前这个骰子可能掷出的点数
                    if (s - j > 0) {//没有骰子能投掷出小于等于 0 的点数
                        dp[i][s] += dp[i - 1][s - j];//当前n个骰子出现的点数之和等于n-1个骰子出现的点数之和加上这一个出现的点数
                    } else {
                        break;
                    }
                }
            }
        }
        double total = Math.pow((double) 6, (double) n);//有多少种点数配对情况,比如两骰子有6²=36种配对情况,也即骰子出现的和的次数为36次
        double[] ans = new double[5 * n + 1];//n个骰子「点数和」的范围为 [n, 6n] ,数量为 6n - n + 1 = 5n + 1 种。
        for (int i = n; i <= 6 * n; i++) {
            ans[i - n] = ((double) dp[n][i]) / total;//第i小的点数出现的概率
        }
        return ans;
    }
}

文章作者: fFee-ops
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 fFee-ops !
评论
  目录