【LeetCode笔记】剑指 Offer 60. n个骰子的点数(Java、动态规划)

文章目录

  • 题目描述
  • 代码 & 思路
      • 1. 二维数组(方便理解)
      • 2. 一维数组(节约空间)
      • 二刷

鸽了好久的打题博客~要继续补起来了!
今天不打题,明天变垃圾 QAQ

题目描述

  • 一眼就想先暴力枚举、或者递归呀~但是貌似会超时,这里就直接用dp了
  • 参考题解
  • 主要思路有点像跳台阶,也就是用上一轮次的和,来维护当前轮次的值
    【LeetCode笔记】剑指 Offer 60. n个骰子的点数(Java、动态规划)_第1张图片

代码 & 思路

1. 二维数组(方便理解)

  • 举个例子吧:两个骰子得出来的值8,相当于:
    1. 一个骰子的2,再补上另一个的6
    2. 一个骰子的3,再补上另一个的5
    3. 一个骰子的4,再补上另一个的4
    4. 一个骰子的5,再补上另一个的3
    5. 一个骰子的6,再补上另一个的2
    6. 当然,一个骰子的1,再补上另一个的7 这种情况是不存在的,所以只有5种情况可行。
      也就是说 dp[2][8] = dp[1][2] + dp[1][3] + dp[1][4] + dp[1][5]+ dp[1][6]
  • 按照这么一个思路,我们就可以得出下面的代码:
class Solution {
    public double[] dicesProbability(int n) {
        // 第一行、最后一列舍弃,方便下标理解
        // dp[骰子数][可投出点数] = 对应次数
        double[][] dp = new double[n + 1][6 * n + 1];
        // 初始化
        for(int i = 1; i <= 6; i++) {
            dp[1][i] = 1;
        }
        
        // dp 过程
        for(int i = 2; i <= n; i++) {
            // 范围[i, 6 * i]
            for(int j = i; j <= 6 * i; j++) {
                // 取上一层的前6个
                for(int k = 1; k <= 6; k++) {
                    // 取不到6个则有多少取多少
                    if(j - k <= 0) {
                        break;
                    }
                    // 状态转移方程 
                    dp[i][j] += dp[i - 1][j - k];
                }
            }
        }

        // 结束,取答案
        double all = Math.pow(6, n);
        double[] ans = new double[5 * n + 1];
        for(int i = 0; i < ans.length; i++) {
            ans[i] = dp[n][i + n] / all;
        }
        return ans;
    }
}

2. 一维数组(节约空间)

  • 在上一个方法的基础上,我们知道:第 n 轮的值,只和第 n - 1 轮的值相关
  • (还是得去上面链接的题解结合动图理解
  • 经过思考,可以发现其实按照从后往前的顺序进行数组更新,就只需要一维数组即可:
  • 举个例子(实际还是1维,这里的行值是辅助了解当前状态):
    1. dp[2][12] = dp[1][11] + dp[1][10] + … + dp[1][6]
    2. dp[2][11] = dp[1][10] + dp[1][9] + … + dp[1][5]
    3. dp[2][2] = dp[1][1]
    4. 这也就是需要从后往前的原因,如果从前往后的话,dp[2][12]就取不到dp[1][11]的值了。从后往前可以实现无后效性~
  • 那么进行一个空间上的优化,可以得到以下代码:
class Solution {
    public double[] dicesProbability(int n) {
        // 0 下标不取,一共 6 * n 个格子
        double[] dp = new double[6 * n + 1];
        // 初始化,全为1
        for(int i = 1; i <= 6; i++) {
            dp[i] = 1;
        }
        
        // 2个、3个...n个骰子,以此类推
        for(int i = 2; i <= n; i++) {
            // 从后往前,可推n个骰子范围[n, 6 * n]
            for(int j = 6 * i; j >= i; j--) {
                // 给新的格子赋值
                dp[j] = 0;
                // 获取前六个格子的值(类似走楼梯)
                for(int k = 1; k <= 6; k++) {
                    // 过于倒退的情况,结束(不一定都能取6个)
                    if(j - k < i - 1) {
                        break;
                    }
                    // 累加前面的格子值
                    dp[j] += dp[j - k];
                }
            }
        }

        // 获取答案
        double all = Math.pow(6, n);
        double[] ans = new double[5 * n + 1];
        for(int i = 0; i < ans.length; i++) {
            ans[i] = dp[n + i] / all;
        }
        return ans;
    }
}

二刷

  • O( n 2 n^2 n2)、O( n 2 n^2 n2)
class Solution {
    public double[] dicesProbability(int n) {
        // 1. dp初始化
        int[][] ans = new int[n + 1][6 * n + 1];
        for(int i = 1; i <= 6; i++) {
            ans[1][i] = 1;
        }

        // 2. dp 过程 O(n^2)
        for(int i = 2; i <= n; i++) {
            for(int j = i; j <= i * 6; j++) {
                for(int k = 1; k <= 6 && j - k >= i - 1; k++) {
                    ans[i][j] += ans[i - 1][j - k];
                }
            }
        }

        // 3. 获取答案
        double[] myAns = new double[5 * n + 1];
        for(int i = 0; i < myAns.length; i++) {
            myAns[i] = ans[n][i + n] / Math.pow(6, n);;
        }
        return myAns;
    }
}
  • O( n 2 n^2 n2)、O( n n n)
class Solution {
    public double[] dicesProbability(int n) {
        // 1. dp初始化
        int[] ans = new int[6 * n + 1];
        for(int i = 1; i <= 6; i++) {
            ans[i] = 1;
        }

        // 2. dp 过程 O(n^2)
        for(int i = 2; i <= n; i++) {
            for(int j = 6 * i; j >= i; j--) {
                ans[j] = 0;
                for(int k = 1; k <= 6 && j - k >= i - 1; k++) {
                    ans[j] += ans[j - k];
                }
            }
        }

        // 3. 获取答案
        double[] myAns = new double[5 * n + 1];
        for(int i = 0; i < myAns.length; i++) {
            myAns[i] = ans[i + n] / Math.pow(6, n);;
        }
        return myAns;
    }
}

你可能感兴趣的:(LeetCode要每天都刷噢,java,算法,动态规划,leetcode)