n个骰子的点数

题目:把 n 个骰子扔在地上,所有骰子朝上一面的点数之和为 s ,输入 n ,打印出 s 的所有可能的值出现的频率。

下面是《剑指offer》中的分析和代码

解法一:是基于递归的,这种方法当 n 小的时候可以,但是当 n 很大的话,速度变得很慢。

/*****************************************************************/

分析:玩过麻将的都知道,骰子一共6个面,每个面上都有一个点数,对应的数字是1 6之间的一个数字。所以,n个骰子的点数和的最小值为n,最大值为6n。因此,一个直观的思路就是定义一个长度为6n-n的数组,和为S的点数出现的次数保存到数组第S-n个元素里。另外,我们还知道n个骰子的所有点数的排列数6^n。一旦我们统计出每一点数出现的次数之后,因此只要把每一点数出现的次数除以6^n,就得到了对应的概率。

该思路的关键就是统计每一点数出现的次数。要求出n个骰子的点数和,我们可以先把n个骰子分为两堆:第一堆只有一个,另一个有n-1个。单独的那一个有可能出现从16的点数。我们需要计算从16的每一种点数和剩下的n-1个骰子来计算点数和。接下来把剩下的n-1个骰子还是分成两堆,第一堆只有一个,第二堆有n-2个。我们把上一轮那个单独骰子的点数和这一轮单独骰子的点数相加,再和剩下的n-2个骰子来计算点数和。分析到这里,我们不难发现,这是一种递归的思路。递归结束的条件就是最后只剩下一个骰子了。

/**************/*********************************/

int g_maxValue = 6;
void Probability(int original, int current, int sum, int* pProbabilities)	// 递归,代码顺序做了调整
{
	if(1 == current)
		pProbabilities[sum - original]++;
	else
	{
		for(int i = 1; i <= g_maxValue; ++i)
			Probability(original, current - 1, i + sum, pProbabilities);
	}
}
void Probability(int number, int* pProbabilities)
{
	for(int i = 1; i <= g_maxValue; ++i)
		Probability(number, number, i, pProbabilities);
}

void PrintProbability1(int number)
{
	if(number < 1)
		return;
	int maxSum = number * g_maxValue;
	int* pProbabilities = new int[maxSum - number + 1];
	for(int i = number; i <= maxSum; ++i)	// 初始化为0
		pProbabilities[i - number] = 0;

	Probability(number, pProbabilities);
	int total = pow((double)g_maxValue, number);
	for(int i = number; i <= maxSum; ++i)
	{
		double ratio = (double)pProbabilities[i - number]/total;
		printf("%d: %e\n", i, ratio);
	}
	delete [] pProbabilities;
}

解法二:基于循环的,速度可以接受

假设我们只有一个骰子, 那么可以 s 只能取 1,2,3,4,5,6 并且每个s的次数都为1;现在我们有另外一个骰子,那么现在的 s 可能值为 ,2,3,4,5,6,7,8,9,10,11,12;

我们定义两个大小一样的数组,并让数组中下标为 n 的值表示和为 n 出现的次数。现在有两个骰子 数组大小应该为 6 * 2 + 1


下标0123456789101112
arr[0]0111111000000
arr[1]0012345654321

显然,可以得到从arr[0]得到啊arr[1]的规律,即当有一个新骰子出现,那么此时和为 n 的骰子出现次数应该为上一次骰子出现的和为 (n-1)、(n-2)、(n-3)、(n-4)、(n-5)、(n-6)之和。

那么假设我们有 N 个骰子,定义两个大小为 6 * N + 1 的数组,循环求出1个骰子、2个骰子、3个骰子。。。N 个骰子的每个和出现的次数。即可得到每个和出现的概率。

代码(来自《剑指offer》)

// 循环
void PrintProbability2(int number)
{
	if(number < 1)
		return;

	int* pProbabilities[2];
	pProbabilities[0] = new int[g_maxValue * number + 1];
	pProbabilities[1] = new int[g_maxValue * number + 1];
	for(int i = 0; i < g_maxValue*number + 1; ++i)
	{
		pProbabilities[0][i] = 0;
		pProbabilities[1][i] = 0;
	}
	int flag = 0;
	for(int i = 1; i <= g_maxValue; ++i)
		pProbabilities[flag][i] = 1;
	for(int k = 2; k <= number; ++k)
	{
		for(int i = 0; i < k; ++i)
			pProbabilities[1 - flag][i] = 0;

		for(int i = k; i <= g_maxValue * k; ++i)
		{
			pProbabilities[1 - flag][i] = 0;
			for(int j = 1; j < i && j <= g_maxValue; ++j)
				pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];
		}
		flag = 1 - flag;
	}

	int total = pow((double)g_maxValue, number);
	for(int i = number; i <= g_maxValue * number; ++i)
	{
		double ratio = (double)pProbabilities[flag][i] / total;
		printf("%d: %e\n", i, ratio);
	}
	delete [] pProbabilities[0];
	delete [] pProbabilities[1];
}

解法三:直接用概率来循环,循环原理跟解法二一致。

代码如下:


void  PrintProbability3(int N){
	if( N < 1)
		return;
	double * a[2];
	a[0] = new double[6 * N + 1];
	a[1] = new double[6 * N + 1];
	for(int i = 0; i < 6 * N + 1; ++i)	//初始化数组
	{
		a[0][i] = 0.0;
		a[1][i] = 0.0;
	}
    for (int i = 1;i <= 6; i++) //一个骰子的时候,每个和概率都是1/6
		a[1][i] = 1.0/6; 
	int flag = 1;
    for (int k = 2; k <= N; k++) //循环直到求出N个骰子
	{
		for(int i = 0; i < k; ++i)
			a[1-flag][i] = 0.0;
        for (int i = k;i <= 6*N; i++) 
		{
			a[1-flag][i] = 0.0;
			int j = 1;
            while ( j < i && j <= 6 )   
				a[1-flag][i] += a[flag][i-j++]/6;
        }
        flag = 1-flag;
    }
    for (int i = N;i <= 6*N;i++) 
		printf("%d : %e\n", i , a[flag][i]);
} 





  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值