把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;
}
}
运行结果: