把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
通过次数19,160提交次数35,573
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路:
动态规划,题目的意思比较绕,可以这样来理解。掷n个骰子,可能的点数是n~6*n,求的是每个点数出现的概率。而易得,n个骰子,总共有6^n种可能性,所以,我们要求的是每个点数出现的次数即可求得概率。考虑使用动态规划求解:
1. 确认状态。dp[i][j]表示投了i个骰子之后,点数为j出现的次数;
2. 状态转移方程。考虑投了n-1个骰子,第n个骰子有1-6个点数的可能性,那么对dp[n-1][j-1]~dp[n-1][j-6]求和,即为dp[n][j]的值;
3. 确认边界条件。即dp[1][1~6]=1;
更进一步的,可以观察到,对于每次状态转移,需要用到的只是前一个阶段的某些状态,因此可以将二维dp数组进行优化,使用一维数组,来进行更新。而为了更新的时候不会影响到需要保存的状态,在每一个阶段(投新的骰子),需要从后往前进行更新。(形象的理解在dp数组中,更新的图是向右下的一个不断扩大的类似阶梯的形状)
注:需要注意的是,再循环体的内部的判断,包括边界值。例如,如果当前是第i个骰子
//二维
class Solution {
public:
vector<double> twoSum(int n) {
int dp[12][73];
memset(dp,0,sizeof(dp));
for(int i = 1;i<=n;i++)
{
if(i==1)
{
for(int j = 1;j<7;j++)
{
dp[i][j] = 1;
}
}
else
{
for(int j = i;j<=6*i;j++)
{
for(int k = 1;k<7;k++)
{
if(j-k<=0)break;
dp[i][j]+=dp[i-1][j-k];
}
}
}
}
vector<double> ret;
int all = pow(6,n);
for(int i = n;i<=6*n;i++)
{
ret.push_back((double)dp[n][i]/(double)all);
}
return ret;
}
};
//一维
class Solution {
public:
vector<double> twoSum(int n) {
vector<int> dp(70,0);
for(int i = 1;i<=6;i++)
{
dp[i]=1;
}
for(int i = 2;i<=n;i++)
{
for(int j = i*6;j>=i;j--)
{
dp[j]=0;
for(int k =1;k<=6;k++)
{
if(j-k<i-1)
{
break;
}
//cout<<j-k<<endl;
dp[j]+=dp[j-k];
}
}
}
vector<double> ret;
int all = pow(6,n);
for(int i = n;i<=6*n;i++)
{
ret.push_back((double)dp[i]/(double)all);
}
return ret;
}
};