剑指Offer:面试题43 n个骰子的点数

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

骰子一共6个面。n个骰子的和的最小值为n,最大值为6n。n个骰子的所有点数的排列数为6^n。我们需用统计
每一个点数出现的次数,然后把每一个点数出现的次数除以6^n,就能求出每个点数出现的概率。

递归解法:
如何统计每个点数出现的次数?
可以把n个骰子分成两堆,第一堆只有1个,第二堆有n-1个。单独的那一个可能出现1到6的点数+剩下的n-1个骰子
的点数和
接下来把n-1个骰子再分成1和n-2,重复上述过程。
。。。
3个骰子分成1和2
2个筛子分成1和1
f(n) = f(n-1) + f(n-2) + f(n-3) + f(n-4) + f(n-5) + f(n-6)

基于循环求骰子点数:
用两个数组存储骰子点数的每一个总数出现的次数。在一次循环中,第一个数组中的第n个数字表示骰子和为n
出现的次数。在下一个循环中,加上一个新的筛子,此时和为n的骰子出现的次数应该等于上一次循环中
骰子点数和为n-1、n-2、n-3、n-4、n-5、n-6的次数的总和
牛的一比啊,的确是这样的。你想好了,因为后面会出现0~6,6种可能,会在原有基础上面累加形成。

所以把另一个数组的第n个数字设为前一个数组的第n-1,n-2,...,n-6之和。

定义一个长度为6n-n+1的数组,和为s的点数出现的次数保存到数组第s-n个元素中

输入:
输入有多组数据。
每组数据一行,包含2个整数n(0<=n<=10),m(6<=m<=8),n表示YZ拿出的骰子数,m表示骰子拥有的点数。如果n=0,则结束输入。
输出:
对应每组数据,输出ZL最可能依次写下的点数,以及其对应的概率值。概率值按4舍5入要求保留2位小数。每组数据之间空一行,注意:最后一组数据末尾无空行。
样例输入:
1 6
4 6
3 7
0
样例输出:
1 0.17
2 0.17
3 0.17

13 0.11
14 0.11
15 0.11

12 0.11
10 0.10
11 0.10

关键:
用划分又他妈改变了数组原来的下标,这样想要求的数字不知道是谁了。除非用结构体,把最大的几个搞出来
只能用稳定的,用一个set来做,只能这样,还可以
用冒泡排序来做,因为最多81个数字,然后,冒泡一轮确定一个最大的数,同时输出它的下标
*/

/*
关键:未能做出
1 用两个数组存储骰子点数的每一个总数出现的次数。在一次循环中,第一个数组中的第n个数字表示骰子和为n
出现的次数。在下一个循环中,加上一个新的筛子,此时和为n的骰子出现的次数应该等于上一次循环中
骰子点数和为n-1、n-2、n-3、n-4、n-5、n-6的次数的总和
2  int* pDice[2];//函无指有,这里是指针数组,pDice是一个含有两个元素的数组,不过这两个元素都是指针类型
3  for(int i = 1 ; i <= iDotNum ; i++)//设定第一个骰子的初始值
 {
  pDice[iFlag][i] = 1;
 } 
4  for(int k = 2 ; k <= iDiceNum ; k++)//iDiceNum个骰子,因此构造大循环,由于第一次已经赋值结束了,所以这里从第二次开始
 {
  for(int j = 0 ; j < k; j++)//对于骰子取不到的值,直接赋值为0
  {
   pDice[1-iFlag][j] = 0;
  }
  for(int i = k ; i <= iDotNum*k ; i++)//对k到iDotNum*k之间的所有元素进行赋值
  {
   pDice[1-iFlag][i] = 0;
   for(int j = 1 ; j <= i && j <= iDotNum ; j++)//更新数组元素的值,这里的j类似与n-1,n-2,..,n-iDotNum中的变量
   {
    pDice[1-iFlag][i] += pDice[iFlag][i-j];
   }
*/

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <algorithm>

using namespace std;

const int MAXSIZE = 80 + 1;//最多10个骰子,最大点数为8,所以数组的总元素个数至多为8*10+1

typedef struct Dice
{
 Dice():_iNum(0),_iTimes(0){}
 Dice(int iNum,int iTimes):_iNum(iNum),_iTimes(iTimes){}
 bool operator < (const Dice& other) const 
 {
  if(_iTimes != other._iTimes)
  {
   return _iTimes < other._iTimes;
  }
  else
  {
   return _iNum > other._iNum;
  }
 }
 void set(int iNum,int iTimes)
 {
  _iNum = iNum;
  _iTimes = iTimes;
 }
 //Dice& operator = (const Dice& other)
 //{
 // if(this != &other)
 // {
 //  _iNum = other._iNum;
 //  _iTimes = other._iTimes;
 // }
 // return *this;
 //}
 //bool operator <= (const Dice& other)
 //{
 // return _iTimes <= other._iTimes;
 //}
 int _iNum;//哪个点数
 int _iTimes;//点数出现的次数
}Dice;
Dice diceArr[MAXSIZE];


void swap(Dice* pNum1,Dice* pNum2)
{
 Dice iTemp = *pNum1;
 *pNum1 = *pNum2;
 *pNum2 = iTemp;
}

int randomInRange(int min,int max)
{
 return (rand() % (max - min + 1) + min);
}


//template<typename T>
//直接用快排,然后取倒数3个么。从后向前,把小于的放到后面,大于的放到前面,不就可以了吗,不行,把这些数的下标变了,而我们是需要下标的,坑爹了
//int partition(Dice* pArr,int low,int high)//并且选取最大的3个数,那么应该自定义排序为
//{
// int iIndex = randomInRange(low,high);
// swap(&pArr[low],&pArr[iIndex]);
// Dice iAxis = pArr[low];
// while(low < high)
// {
//  while(low < high && pArr[high] <= iAxis)
//  {
//   high--;
//  }
//  pArr[low] = pArr[high];
//  while(low < high && iAxis <= pArr[low])
//  {
//   low++;
//  }
//  pArr[high] = pArr[low];
// }
// pArr[low] = iAxis;
// return low;
//}
//
//void maxKNum(Dice* pArr,int low,int high,int k)
//{
// int iIndex = partition(pArr,low,high);
// int iRes = k - 1 + low;
// while(iIndex !=  iRes)//因为下标从0开始,当下标为k-1时,此时实际上有k个数已经排好了,这里数组无序之后,不能直接用k-1,必须加上low
// {
//  if(iIndex < iRes)//如果下标小于k-1,说明应该将low变为iIndex+1,并且继续划分
//  {
//   low = iIndex + 1;
//  }
//  else
//  {
//   high = iIndex - 1;
//  }
//  iIndex = partition(pArr,low,high);
// }
//}

//思路:
//刚开始声明两个数组,并对数组初始化为0,设置第一个数组1到6为1,开始进行筛子个数个循环(从第二层开始循环),
//尤其注意,比如两个骰子,筛子点数之和的下标为0,1都取不到,至少为2
void diceProbability(int iDiceNum,int iDotNum)
{
 if(iDiceNum < 1)
 {
  return;
 }
 int* pDice[2];//函无指有,这里是指针数组,pDice是一个含有两个元素的数组,不过这两个元素都是指针类型
 int iDice1Arr[MAXSIZE];
 int iDice2Arr[MAXSIZE];
 pDice[0] = iDice1Arr;
 pDice[1] = iDice2Arr;
 int iFlag = 0;//靠,这里果然是得用一个二级指针来做,否则选取的两个数组就写死了
 memset(iDice1Arr,0,sizeof(iDice1Arr));
 memset(iDice2Arr,0,sizeof(iDice2Arr));
 for(int i = 1 ; i <= iDotNum ; i++)//设定第一个骰子的初始值
 {
  pDice[iFlag][i] = 1;
 } 
 for(int k = 2 ; k <= iDiceNum ; k++)//iDiceNum个骰子,因此构造大循环,由于第一次已经赋值结束了,所以这里从第二次开始
 {
  for(int j = 0 ; j < k; j++)//对于骰子取不到的值,直接赋值为0
  {
   pDice[1-iFlag][j] = 0;
  }
  for(int i = k ; i <= iDotNum*k ; i++)//对k到iDotNum*k之间的所有元素进行赋值
  {
   pDice[1-iFlag][i] = 0;
   for(int j = 1 ; j <= i && j <= iDotNum ; j++)//更新数组元素的值,这里的j类似与n-1,n-2,..,n-iDotNum中的变量
   {
    pDice[1-iFlag][i] += pDice[iFlag][i-j];
   }
  }
  iFlag = 1 - iFlag;
 }

 //将出现次数的数组转化为结构体来做
 memset(diceArr,NULL,sizeof(diceArr));
 for(int i = iDiceNum ; i <= iDotNum*iDiceNum ; i++)
 {
  diceArr[i].set(i,pDice[iFlag][i]);
 }
 sort(diceArr + iDiceNum,diceArr + iDotNum*iDiceNum + 1);
 //接下来选取次数最多的三个,题目转化为了在一个一维数组乱序数组中,寻找值最大的3个数,输出其下标,变成了寻找max3那道题目,可以采用partition来做,不需要用快排
 //maxKNum(diceArr,iDiceNum,iDotNum*iDiceNum,3);//上标是iDiceNum,下标是iDotNum*iDiceNum,因为partition的下标是取到的,类似与快排
 double total = pow(double(iDotNum),iDiceNum);
 for(int i = iDotNum*iDiceNum ; i >  iDotNum*iDiceNum - 3; i--)
 {
  printf("%d %.2f\n",diceArr[i]._iNum,1.0*diceArr[i]._iTimes/total);
 }
}

void process()
{
 int n,m;
 while(EOF != scanf("%d %d",&n,&m))
 {
  if(n == 0)
  {
   break;
  }
  if(n < 0 || n > 10 || m < 6 || m > 8)
  {
   continue;
  }
  diceProbability(n,m);
 }
}

int main(int argc,char* argv[])
{
 process();
 getchar();
 return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值