1. 程式人生 > >《劍指offer》:[43]N個骰子的點數

《劍指offer》:[43]N個骰子的點數

題目:把N個骰子扔在地上,所有骰子朝上一面的點數之和為S,輸入N,打印出S的所有可能的值出現的概率。
分析:對於6點的N個骰子來講,其和S的最小值為N,最大值為6N。要得到和S出現的概率,就得得到和S出現的次數,然後用某一S出現的次數/S的所有可能性=S出現的概率。對於骰子進行全排列,我們很容易知道S的所有可能性為:6^N。那麼問題來了,怎麼計算出S出現的次數呢?
方案一:遞迴法。由於不能一口氣吃一個大胖子,所以我們必須把事情一步步的解決。該方法的思路是將N個骰子分成兩部分,第一部分是一個骰子,另一部分是剩下的N-1個。我們先計算第一部分第一個骰子出現的點數,很明顯,有1-6這6種可能性;然後再計算這一已知的部分和剩下的N-1個骰子出現的點數之和。仿照前例,我們可以將第二部分分解為第一部分(1個)和第二部分(剩下的N-2)。我們把上一輪和這裡的第一部分的和相加,得到新的點數之和,再將新的點數之和和剩下的N-2部分進行加法計算出新的點數之和。這就是典型的遞迴思想。結束條件是隻剩下最後一個骰子。
方案二:迴圈法。
該方法的主要思路是用兩個陣列來求的骰子值可能出現的次數,是一種空間換時間的做法。關鍵思想是:在一次迴圈中,第一個陣列中的第N個數字表示骰子和為N出現的次數。下一次迴圈中,我們加上一個新的骰子,此時和為S的骰子出現的次數應該等於第一個陣列中S-1,S-2,S-3,S-4,S-5,S-6的次數之和。這樣用一個數組來記錄上一步的結果,而下一步的結果要用到上一步的結果。該方法避免了複雜多餘的計算。
具體實現程式碼如下:
#include <iostream>
#include <math.h>
#include <stdlib.h>
using namespace std;
int MaxValue=6;
void Probabilityhelp(int original,int current,int sum,int *probabilitys)
{
	if(current==1)//當只剩下最後一個骰子的時候,計算其和;
	{
		probabilitys[sum-original]++;//和為sum的陣列+1;
	}
	else
	{
		for(int i=1;i<=MaxValue;i++)
		{
			Probabilityhelp(original,current-1,sum+i,probabilitys);//剩下的繼續遞迴;
		}
	}
}
void Probability(int number,int *pProbability)
{
	for(int i=1;i<=MaxValue;i++)
		Probabilityhelp(number,number,i,pProbability);
}
void PrintProbability(int number)
{
	if(number<1)
		return;
	int MaxSum=number*MaxValue;
	int *pProbability=new int[MaxSum-number+1];//我們定義一個6N-N+1的陣列,和為S的點數出現的次數儲存到陣列第S-N個元素裡。
	for(int i=number;i<=MaxSum;i++)
		pProbability[i-number]=0;//次數都初始化為0次;
	Probability(number,pProbability);
	int total=pow((double)MaxValue,number);//總共出現的值的可能性;
	for(int i=number;i<=MaxSum;i++)
	{
		double ratio=(double)pProbability[i-number]/total;//出現的次數/總的可能性;
		cout<<i<<" "<<ratio<<endl;
	}
	int a=1;
	int b=2;
	delete []pProbability;
}


int main()
{
	PrintProbability(6);
	system("pause");
	return 0;
}

執行結果:


由於該方法有許多地方的計算是重複的,和前面講到過的【9】斐波拉切數列以及【39-1】中判斷是否為平衡二叉樹一樣,存在重複計算導致效率低下。
所以下面採用一種迴圈的方式來解決此問題,也是時間換空間的方法。

方案二程式碼實現:
#include <iostream>
#include <math.h>
using namespace std;
int g_maxValue=6;
void PrintProbability(int n)
{
	if(n<1)
		return;
	int* pProbability[2];
	pProbability[0]=new int[g_maxValue*n+1];
	pProbability[1]=new int[g_maxValue*n+1];
	for(int i=0;i<=g_maxValue*n;i++)
	{
		pProbability[0][i]=0;
		pProbability[1][i]=0;
	}
	int flag=0;
	for(int i=1;i<=g_maxValue;i++)
		pProbability[flag][i]=1;
	for(int k=2;k<=n;k++)
	{
		for(int i=0;i<k;i++)
			pProbability[1-flag][i]=0;
		for(int i=k;i<=g_maxValue*k;i++)
		{
			pProbability[1-flag][i]=0;
			for(int j=1;j<=i && j<=g_maxValue;j++)
				pProbability[1-flag][i]+=pProbability[flag][i-j];//這一步就是求和為S時的次數為上一步S-1,S-2,S-3,S-4,S-5,S-6的總次數;
		}
		flag=1-flag;
	}
	int total=pow((double)g_maxValue,n);
	double prob=0;
	for(int i=n;i<=g_maxValue*n;i++)
	{
		double ratio=(double)pProbability[flag][i]/total;
		prob+=ratio;
		cout<<i<<" "<<ratio<<" "<<endl;
	}
	cout<<"校驗和是否1:"<<prob<<endl;
	cout<<endl;
	delete[] pProbability[0];
	delete[] pProbability[1];
}


int main()
{
	PrintProbability(2);
	system("pause");
	return 0;
}

執行結果:


此種方法的時間效率相對於方案有所提高,但是藉助了輔助空間。