週末閒來沒事,就把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循環就解決了,這個也不是難事。
題目就做到這裏吧。