【DP】LeetCode 剑指 Offer 60. n个骰子的点数

题目链接

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

思路

引子

根据概率计算公式,点数 k 出现的概率为

\[P_{(k)}=k \text { 出现的次数/总次数 } \]

我们很容易算出来 n 个骰子投掷的结果总共有 \(6^n\)种,所以我们只需要再算出来点数 k
出现的次数就能算出来概率。

在动态规划问题中,主要有三个步骤:

  • 表示状态
  • 找出状态转移方程
  • 边界处理

表示状态

动态规划问题中,只用考虑第 n 个阶段。分析问题的状态时,不要分析整体,只分析最后一个阶段即可!因为动态规划问题都是划分为多个阶段的,各个阶段的状态表示都是一样,而我们的最终答案在就是在最后一个阶段。

对于这道题,最后一个阶段是什么呢?

通过题目我们知道一共投掷 n 枚骰子,那最后一个阶段很显然就是:当投掷完 n 枚骰子后,各个点数出现的次数。

可以看到在 dp 数组中要表示的元素有以下三种:

  • 骰子的个数
  • 点数
  • 点数出现的次数

根据这个思路很容易想到使用二维数组来存储前两个元素,对应的值为第三个元素。即 \(dp[n][j]\) 的含义为 n 个骰子投出点数为 j 的次数注意这里的点数指的是点数之和,而不是单个的点数

找状态转移方程

在找状态转移方程的时候还是只用考虑最后一个阶段,即 n 个骰子的结果怎么由 n-1 个骰子的结果转移过来。

可以将 n 个骰子的投掷结果视为在 n-1 个骰子的基础上多投掷了一个骰子,然后把这个骰子与 n-1 个骰子的对应情况进行枚举相加。用公式表示就是

\[d p[n][j]=\sum_{i=1}^6 d p[n-1][j-i],i \in \{1,2,3,4,5,6\} \]

这样我们就构建出了条件转移方程。

边界处理

接下来就需要找出 dp 数组的初始状态。

我们可以直接知道的状态是啥,就是第一阶段的状态:投掷完1枚骰子后,它的可能点数分别为 \(1,2,3,\dots,6\),并且每个点数出现的次数都是1。即

\[dp[1][i]=1,i \in \{1,2,3,4,5,6\} \]

空间优化

通过状态转移方程可以看到 \(dp[n]\) 只与 \(dp[n-1]\) 有关,所以可以设置两个一维数组 currentpre 分别代表 \(dp[n]\)\(dp[n-1]\)。不断滚动更新。

代码

dp 数组版本

class Solution {
    public double[] dicesProbability(int n) {
        // 在做 dp 题目的时候只需要考虑最终状态即可,比较容易写出状态表达式和状态转移方程
        // dp[i][j] 表示投掷完 i+1 个骰子之后,点数 j 出现的次数
        // dp[n][j] = sum(dp[n-1][j-i]) 其中 i = (1,2,3,4,5,6)
        double[] result = new double[n * 6 - n + 1];
        int[][] dp = new int[n + 1][n * 6 + 1];

        for(int i = 1; i <= 6; i++){
            dp[1][i] = 1;
        }
        for(int i = 2; i <= n; i++){
            for(int j = i; j <= 6 * i; j++){
                for(int k = 1; k <= 6 && k <= j; k++){
                    dp[i][j] += dp[i - 1][j - k];
                }
            }
        }

        int index = 0;
        for(int i = n; i <= 6 * n; i++, index++){
            result[index] = 1.0 * dp[n][i] / Math.pow(6, n);
        }

        return result;
    }
}

空间优化版本

class Solution {
    public double[] dicesProbability(int n) {
        // 在做 dp 题目的时候只需要考虑最终状态即可,比较容易写出状态表达式和状态转移方程
        // dp[i][j] 表示投掷完 i+1 个骰子之后,点数 j 出现的次数
        // dp[n][j] = sum(dp[n-1][j-k]) 其中 k = (1,2,3,4,5,6)
        double[] result = new double[n * 6 - n + 1];
        int[] pre = new int[n * 6 + 1];
        int[] current = new int[n * 6 + 1];

        for(int i = 1; i <= 6; i++){
            pre[i] = 1;
        }
        for(int i = 2; i <= n; i++){
            for(int j = i; j <= 6 * i; j++){
                for(int k = 1; k <= 6 && k <= j; k++){
                    current[j] += pre[j - k];
                }
            }
            pre = Arrays.copyOf(current, current.length);
            Arrays.fill(current, 0);
        }

        int index = 0;
        for(int i = n; i <= 6 * n; i++, index++){
            result[index] = 1.0 * pre[i] / Math.pow(6, n);
        }

        return result;
    }
}
posted @ 2023-03-30 11:04  Frodo1124  阅读(22)  评论(0编辑  收藏  举报