周末闲来没事,就把csdn的这道朋友的礼物这道题做了,其实这道题就是一个排列组合,然后求概率的问题。先看题目吧。
- 北京
- 难 度 等 级:
n个人,每个人都有一件礼物想送给他人,他们决定把礼物混在一起,然后每个人随机拿走一件,问恰好有m个人拿到的礼物恰好是自己的概率是多少?
输出结果四舍五入,保留8位小数,为了保证精度,我们用字符串作为返回类型。
输入:n,m (0<n<100, 0<=m<=n)
例如:
n = 2,m = 1,输出:0.00000000;
n = 99,m = 0,输出:0.36787944
解题思路,其实这道题已经把解题思路给了出来,举例中,假如99个都是拿到不是自己的礼物,这点提示了我。
先把这道题转换成排列组合,就1~n个数,把它们全部取出来,取出来的结果是:第k次取出来的数恰好是k,而正好有m个这样的概率。
根据提示,我们先考虑这种情况,给你n个数,取出来的结果是,没有一个数对应自己,也就是取第一个数不会取到1,取第二个数不会取到2,。。。。。这样的概率。
其次,我们还要考虑这种情况,从n个数中,取出m个,对应的都是自己。
根据上述分析,这道题就被拆分成两部分了
首先考虑从n个数中,取出m个,对应的都是自己。这个比较好计算出来
比如取第一个数,有n种可能,取到自己的概率是1/n,
第二个数有(n-1)种可能,取到自己的概率是1/(n-1),
..........................
第m个数有(n-m+1)种可能,取到自己的概率是1/(n-m+),
因此,概率就是p0=1/n*1/(n-1)*....................*1/(n-m+1);
注意:这里只是计算从n个取出m个相同,而且是连续的,也就是计算了一次,事实上还有不连续的,这有多少种可能了?这个就是从n个中取出m个数的组合的可能性,因此,还需要乘以次数,次数就是组合了C(n,m)
C(n,m)=n*(n-1)*(n-2)*.....*(n-m+1)/[m*(m-1)*...1]
这样就得到从n个中取出m个相同的概率p1=p0*p1,
这样就得到p1=1/m!;
现在我们再考虑第二部分,剩下的n-m个数,均不能是自己,因为前面拿到的都是自己的,所以,我们就可以考虑这样一个情况,从1~n个数中,拿到的均不是自己的数。
取第一个数,因为不能是自己,所有n-1种可取方式,概率就是(n-1)/n;
这里要考虑取第二个数:有两种情况
第一种情况,取到的是第一个数[1],概率为1/(n-1)
第二种情况,取到的是后面的数【3~n】,概率为(n-2)/(n-1)
当取第二个数为第一种情况时,我们发现剩下的n-2个数,和我们考虑的情况完全一样,相当于降次处理了,这个就是递归调用了。后面就很好处理了。
当取第二个数为第二种情况时,第二个数取到的就是第三个数,剩下的就是【1】和【3~n】,这时,我们发现,它自身开始出现相同的循环了。因此啊,我们就可以构造出第二递归调用函数了。
第一个函数:从n个数中取出一个数,其中,这n个数中包含自己
public static double cal(int n)
{
double result = 0;
double p=0;
if (n ==1)
return 0;
if(n==2)
return 0.5;
p = (double)(n - 1) / n;
result = p * calNo(n - 1);
return result;
}
第二个函数,将从n个数中取出一个数,其中,这n个数中不包含自己
public static double calNo(int n)
{
double result = 0;
if (n == 1)
return 1;
if (noDic.ContainsKey(n))
{
result = noDic[n];
}
else
{
result = (double)1 / n * cal(n - 1) + (double)(n - 1) / n * calNo(n - 1);
if (!noDic.ContainsKey(n))
{
noDic.Add(n, result);
}
}
return result;
}
为了缓存中间值,我们把中间值保存下来,减少计算次数。
第二步的函数两个,ok
下面把第一步的函数补全:
public static double cal(int n, int m)
{
double result = 1;
if (n == 1)
{
return 0;
}
for (int i = 0; i < m; i++)
{
result = (double)result /(m-i);
}
if (m == n)
{
m = n - 1;
}
else
{
result = result * cal(n - m);
}
return result;
}
这里要考虑n==m,当这种情况出现时,就不用第二步的处理
到这里基本就结束了,根据题目要求,吧返回的double转换成string就可以了
整个做下来,3个函数,也可以整合成2个函数,而且步骤都不多,代码就比较少,提交,ok,通过没问题。
其实,从函数的递推关系可以看出,我们直接从最后一步往前推,那么就不需要递归调用了,直接一个for循环就解决了,这个也不是难事。
题目就做到这里吧。