【交替数组】n个骰子的点数

版权声明:本文为博主原创学习笔记,如需转载请注明来源。 https://blog.csdn.net/SHU15121856/article/details/83340128

抽象建模即选择合理的数据结构表述问题;分析模型中的内在规律,并用编程语言表述这种规律。

面试题60:n个骰子的点数

解法一(递归)

用递归的方式,每次拿出一个骰子,对1~6点再调用下一层,递归到底更新总数的频数。基本就是一个排列树穷举,有大量的重复计算。

作者递归出口是1,而且在第一步时没有减1,感觉非常难理解。我这里改成了递归出口是0,表示剩下0个骰子没判断过,在第一步时就减1,表示把第一个骰子判断了。

#include<bits/stdc++.h>
using namespace std;

int g_maxValue = 6;//六面骰子 

//递归函数:递归调用每次拿出一个骰子,六个分支,更新频数数组 
// 参数:
//        original:       骰子总数 
//        current:        当前未计算的骰子数 
//        sum:            前面层已经计算得的骰子数总和
//        pProbabilities: 要更新的频数数组 
void Probability(int original, int current, int sum,
                 int* pProbabilities) {
	if(current == 0) {//递归出口:如果骰子已经都计算过 
		pProbabilities[sum - original]++;//更新频数数组,即对应位置处+1 
	} else {//如果还剩多个骰子 
		for(int i = 1; i <= g_maxValue; ++i) {//再拿出一个骰子,对其的每种取值
			//递归调用以更新频数数组,每个分支中当前骰子数减去这个骰子
			//点数总数是局部自动变量,各自更新为当前点数+之前计算的点数和 
			Probability(original, current - 1, i + sum, pProbabilities);
		}
	}
}

//递归外套
//计算number个骰子朝上点数和的所有可能值出现的概率,保存在pProbabilities数组中
void Probability(int number, int* pProbabilities) {
	for(int i = 1; i <= g_maxValue; ++i)//对第一个骰子的g_maxValue种取值 
		Probability(number, number-1, i, pProbabilities);//分别调用递归函数去更新频数数组 
}

//打印number个骰子朝上点数和的所有可能值出现的概率 
void PrintProbability_Solution1(int number) {
	if(number < 1)//至少1个骰子 
		return;

	int maxSum = number * g_maxValue;//可能出现的最大点数 
	int* pProbabilities = new int[maxSum - number + 1];//点数从number~maxSum,所以数组长n-m+1 
	for(int i = number; i <= maxSum; ++i)
		pProbabilities[i - number] = 0;//总点数为i的频数会保存在i-number里 

	//计算number个骰子朝上点数和的所有可能值出现的概率,保存在pProbabilities数组中 
	Probability(number, pProbabilities);

	int total = pow((double)g_maxValue, number);//n个骰子的所有点数的排列数为g_maxValue的n次方 
	for(int i = number; i <= maxSum; ++i) {//对每个可能的点数 
		double ratio = (double)pProbabilities[i - number] / total;//计算概率=频数/总数 
		printf("%d: %e\n", i, ratio);//打印 
	}
	delete[] pProbabilities;//销毁堆空间的频数数组 
}

int main() {
	PrintProbability_Solution1(3);
	return 0;
}
解法二(交替数组)

用两个数组交替协同第来做,就可以用循环来实现。每次循环加上一个新的骰子,此时和为n的骰子出现的次数就是上一轮的n-1一直加到n-6的次数的总和:
在这里插入图片描述
而没有映射的部分就表示不可能是这个数,比如3个骰子最小是3,下次加了一个骰子,变成4个骰子时候最小就是4了。

#include<bits/stdc++.h>
using namespace std;

int g_maxValue = 6;//六面骰子 

void PrintProbability_Solution2(int number) {
	if(number < 1)//骰子至少有1个 
		return;

	int* pProbabilities[2];//两个数组
	//申请空间 
	pProbabilities[0] = new int[g_maxValue * number + 1];
	pProbabilities[1] = new int[g_maxValue * number + 1];
	//全部初始化为0 
	for(int i = 0; i < g_maxValue * number + 1; ++i) {
		pProbabilities[0][i] = 0;
		pProbabilities[1][i] = 0;
	}
	
	//标识要操作的数组和辅助基准数组,它一改这两个数组的地位就交换了
	int flag = 0; 
	//一开始只有一个骰子,在0号数组中记录抽到1~6的频数都是1 
	for (int i = 1; i <= g_maxValue; ++i)
		pProbabilities[flag][i] = 1;

	//接下来从2个骰子到number个骰子 
	for (int k = 2; k <= number; ++k) {
		//k个骰子最小出k,所以小于k的都设置为0 
		for(int i = 0; i < k; ++i)
			pProbabilities[1 - flag][i] = 0;
		//接下来从k到6k都是可能出现的 
		for (int i = k; i <= g_maxValue * k; ++i) {
			pProbabilities[1 - flag][i] = 0;//先清空为0
			//例如六面骰i=3,这次出现3=上次出现2和上次出现1的
			//例如六面骰i=50,这次出现50=上次出现49~44的
			//所以这里是要注意i比面数还小的时候其实是受制于i的 
			for(int j = 1; j <= i && j <= g_maxValue; ++j)//这里其实应该写j<i而不是j<=i,不过p[flag][0]是0也不影响 
				pProbabilities[1 - flag][i] += pProbabilities[flag][i - j];//都加上来 
		}

		flag = 1 - flag;//标识反转,即下次循环中两个数组角色反转 
	}

	double total = pow((double)g_maxValue, number);//6面骰6^n种组合 
	for(int i = number; i <= g_maxValue * number; ++i) {//遍历所有可能数的频数 
		//计算概率,这里直接用flag因为最后一次for最后一句使flag已经反转了 
		double ratio = (double)pProbabilities[flag][i] / total; 
		printf("%d: %e\n", i, ratio);
	}

	delete[] pProbabilities[0];
	delete[] pProbabilities[1];
}

int main() {
	PrintProbability_Solution2(3);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/SHU15121856/article/details/83340128
今日推荐