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

题目:

剑指 Offer 60. n个骰子的点数
剑指 Offer 60. n个骰子的点数_第1张图片

题解:

点数之和:
剑指 Offer 60. n个骰子的点数_第2张图片
概率:
剑指 Offer 60. n个骰子的点数_第3张图片

1. 解释一:

剑指 Offer 60. n个骰子的点数_第4张图片
剑指 Offer 60. n个骰子的点数_第5张图片
剑指 Offer 60. n个骰子的点数_第6张图片
剑指 Offer 60. n个骰子的点数_第7张图片

2. 解释二:

剑指 Offer 60. n个骰子的点数_第8张图片
剑指 Offer 60. n个骰子的点数_第9张图片

3. 疑难点:

剑指 Offer 60. n个骰子的点数_第10张图片
剑指 Offer 60. n个骰子的点数_第11张图片

代码:

注意:
时间复杂度:O(n * 5n * 6) == O(30 * n²), 又因为1 <= n <= 11,因此,即便是三层for循环,但最大时间复杂度也就是O(3630),也就是一共才循环3000多次,这种效率近似于线性遍历O(N),因此代码执行速度还是很快的。

public class 面试题60 {

    public static double[] twoSum(int n) {
        // 状态定义,dp[i][j]表示骰子个数为i时点数j可能出现的次数,dp[i][s]表示i个骰子掷出s点的次数
        int dp[][] = new int[n + 1][6 * n + 1];
        // 初始状态
        for(int i = 1; i <= 6; i++)
        {
            dp[1][i] = 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 < i - 1) // (总点数 s) - (当前的点数 j) >= (前i-1个骰子的最小点数之和i-1)
                    {
                        break;  // 如果不满足上述条件,则不存在这种情况,直接跳出循环即可
                    }
                    dp[i][s] += dp[i - 1][s - j]; // 当前 n 个骰子出现的点数之和等于前一次出现的点数之和加上这一次出现的点数
                }
            }
        }
        double total = Math.pow(6, n); // 掷出 n 次点数出现的所有情况
        double ans[] = new double[5 * n + 1]; // 因为最大就只会出现 5*n+1 种点数
        for(int i = n; i <= 6 * n; i++)
        {
            ans[i - n] = dp[n][i] / total; // 第i小的点数出现的概率
        }
        return ans;
    }

    public static void main(String[] args) {
        int n = 2;
        double res[] = twoSum(n);
        for(int i = 0; i < res.length; i++)
        {
            System.out.printf("%.5f  ", res[i]);
        }
        System.out.println();
    }
}

参考:

  1. 【n个骰子的点数】:详解动态规划及其优化方式
  2. 动态规划(注释清晰)
  3. 动态规划(扫了一圈,俺是最短的)
  4. C++ 动态规划解法

你可能感兴趣的:(剑指Offer)