之前群里讨论过一道基础算法题,提到过不止一次了,rand5()产生rand7(),看起来很简单,其实也有不少小心机,也算是一个很有趣且巧妙的题目了。虽然网上也有很多答案,但有的并不够循序渐进,所以写篇文章,理下思路。
原题是这样的,“Given a function which produces a random integer in the range 1 to 5, write a function which produces a random integer in the range 1 to 7.”
题目说的很简单,但是蕴含了一些虽然没说,但是大家都默认的一些基本约束,即你只能运用rand5这个已知函数和一些简单的运算符,不能引入一些其它函数或封装特殊运算符。但如果你以为它真的简单,那可能你就要栽了。
我想,应该没有天真烂漫的人会这样实现吧
int rand7(){
return 2+rand5();
}
为什么这样不对,因为很简单,这个函数无法生成1,不符合条件。
那如果这样呢?
int rand7()
{
int i;
i = rand5() + rand5() + rand5() + rand5() + rand5() + rand5() + rand5();
return i%7 + 1;
}
看起来好像没问题,利用5跟7的最小公倍数,7个5,再模7,每个数字概率是1/5,不正好就是1-7嘛。
实际上,这也是错的。
第4行代码中,将会产生5^7种可能的计算,最终这些可能映射到[7,35]的整数区间中,但是[7,35]区间内整数的产生的概率并不相等。看起来有点懵,我们换个更简单的例子来说明下,假如有一个rand3的()函数,3个rand3()能生成[3-9]这个区间的数字,但是[3-9]这个区间的数字是不均匀的。
#多个rand函数
123
123
123
#生成组合
3,4,5,4,5,6,5,6,7
4,5,6,5,6,7,6,7,8
5,6,7,6,7,8,7,8,9
#每个数字概率
3=1
4=3
5=6
6=7
7=6
8=3
9=1
其实,稍微对数字有点敏感的人也能立马判断出来,2+4=6,4+2=6,显然中间的数字有多种组合,出现的概率就会更高。这就引申出一个高中数学的概率问题了,“一对夫妇生了两个孩子 ,其中一个是男孩,问另一个是女孩的概率是多少?”。这两个问题本质上是一样的。
那么正确的做法应该是怎样呢?
思路还是那个思路,rand7的关键不仅是生成1-7之间的数字,还要保证这个数字是随机的,也就是概率均等。做到概率均等,才算正确。
如果这么写呢?
int rand7()
{
int vals[5][5] = {
{1,2,3,4,5},
{6,7,1,2,3},
{4,5,6,7,1},
{2,3,4,5,6},
{7,0,0,0,0}
};
int result = 0;
while(result == 0)
{
int i = rand5();
int j = rand5();
result = vals[i - 1][j - 1];
}
return result;
}
事先构建一个方阵,里面均匀地放着1-7这七个数,然后rand5只用来生成坐标,这样对否?
我们来看是否满足以下两约束:
1.生成1-7这七个数。
2.1-7出现的概率均等
第一个条件,无疑满足。第二个条件,1-7这几个数字,每个数字出现的概率是3/25,虽然这个矩阵里有0这个纯粹用来填充的数字(是0还是10,都不重要),但不妨碍1-7出现的概率均等的约束。
但是这样的写法毕竟太丑,也用了额外的空间,换种方式,其实方阵的做法,转换成更简单的代码,应该是
int rand7()
{
int i;
do{
i = 5 * (rand5() - 1) + rand5(); //产生[1,25]的整数区间
}while(i > 21); //将[1,25]整数区间控制于[1,21]
return i%7 + 1; //将[1,21]映射到[1,7]
}
实际上,这个题是算法导论里的一道题,也是互联网大公司的八股文算法题,是有通用模板的。上面的5和7,可以替换成任意的a,b,只需要满足(a<b && b<2a)即可。
即如果给你Randa, 你可以通过以下方式轻松构造Randb,生成1到b的随机数。
Randb = a * (Randa – 1) + Randa
如果已知rand7,要生成rand5呢?很简单,把6和7排除掉就是rand5了,也就是当随机到大于5的时候,就再随机一次,直到小于等于5。
int rand5(){
Rint result = rand7()+1;
while (result > 5){
result = rand7()+1;
}
return result;
}
很简单,但也是正确的做法。其实跟上面说的rand5生成rand7的思路是一样的,先利用rand5把范围放大,然后再排除掉超出的范围,使这个范围是7的倍数。
对于b>=2a的情况,可自行推导
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。