LeetCode 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof

通过观察题目,我们一开始想到的是一个深度优先搜索问题,所以利用递归实现,然后写完代码如下:

class Solution {
    int[] book;//表示第几颗色子已经扔过了
    int total;//总数
    int[] points;//表示点数的数组
    int sum = 0;//用于统计点数
    double[] tar;
    public double[] dicesProbability(int n) {
          book = new int[n];
          total = (int)Math.pow(6,n); //获取情况的总数
          points = new int[6 * n + 1];//由于n颗色子,那么点数的范围就是n - 6n,所以为了使得点数就是下标,points长度为6  * n + 1
          tar = new double[5 * n + 1];
          dfs(0,n);
         return tar;
    }
    public void dfs(int step,int n){
        if(step == n){
            //如果已经扔了n颗,那么统计点数出现的次数,然后计算对应的概率
            points[sum]++;
            int index = sum - n; 
            tar[index] = (points[sum] + 0.0) / total;
            return;
        }
        int k;
        //第step颗色子一共有6种可能
        for(k = 1; k <= 6; k++){
            sum += k;
            book[step] = k;
            dfs(step + 1,n);//扔完这一颗之后,进入递归,扔下一颗
            sum -= k;
        }
    }
}

好的,测试提交的时候,发现会超时。因为一颗色子有6种情况,所以有n颗,因此需要运行6^n次,即时间复杂度是O(6 ^ n),因此会发生超时,所以不适用。那么应该怎样做,才可以避免这个问题呢?

我们扔一颗色子的概率必定是1/6,那么给出n颗色子,要求抛出点数为x的概率怎么求?请看下面的图示:
在这里插入图片描述

因此这一道题利用了动态规划。

1、状态方程:定义一个二维数组,其中行表示第几颗色子,列表示的是扔完n颗色子之后得到的点数。由于有n颗色子,所以定义的数组是dp[n + 1][6 * n + 1],因为点数的范围是[ n, 6 * n].
2、转移方程: dp[n][k] += dp[n - 1][k - i],其中i表示第n颗色子扔的所有可能,所以对应的范围时[1,6]
3、返回值:由于dp数组表示点数x出现的可能的数目,所以还需要重新定义一个数组target,表示各个点数出现的概率,由于n颗色子得到的点数是[n,6 * n],即一共有5 * n + 1种可能,那么target就是new double[5 * n+ 2]然后将其返回。

过程分析:
在这里插入图片描述

对应的代码:

class Solution {
    public double[] dicesProbability(int n) {
           /*
           定义一个二维数组,用于存放点数对应的可能性,为了使得第n颗色子对
           应的是i,所以定义的数组有n + 1行,点数的范围是[n,6n],所以有6 * 
           n + 1列
          */
         int[][] arr = new int[n + 1][6 * n + 1];
         int i,j,k = 1,total;
         total = (int)Math.pow(6,n);
         /*
         初始化第一颗色子点数对应的可能为1,因为如果只有一棵色子的时
         候,对应的点数对应只有一种可能
         */
         for(i = 1; i <= 6; i++){
            arr[1][i] = 1;
         }
         //如果超过了2颗色子,那么需要从i = 2开始遍历了
         for(i = 2; i <= n; i++){
             for(k = i; k <= 6 * i; k++){ //第i颗色子扔了之后,对应的点数范围是[i,6 * i]
               for(j = 1; j <= 6; j++){//j表示第i颗色子扔的可能性
               /*
               前 i - 1颗色子扔的点数必须是大于等于i - 1点,因为前面一共有
               i - 1颗色子,那么是这些色子都是1点的时候,点数最小(i - 
               1),所以需要判断这些色子的点数是否符合要求
               */
                    if(k - j  + 1 < i) //前面i - 1颗色子的点数小于i - 1的时候,退出循环
                       break;
                    arr[i][k] += arr[i - 1][k - j];
                }
             }
         }
         i = 0;
         double[] result = new double[5 * n + 1];//点数[0,6n],所以点数一共有5 * n + 1种可能
         for(k = n; k < arr[n].length; k++)
            result[i++] = (arr[n][k] + 0.0) / total;
         return result;
    }
}

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值