在网上看到一道有意思的算法题
题目如下:
已知有个rand7()的函数:可以返回1到7之间的随机正整数;
利用这个rand7()构造出rand10() 方法,要求能返回1~10之间的随机正整数。
这道题目隐藏的要求:
1-10出现的概率必须都是1/10,否则不符合‘随机’特性;
先分析下需要得到的1-10,这是10个连续的正整数,也就是说我们得构造出10个,20个,30个。。。连续的正整数,才能变形为10个连续的正整数(而且你应该能想到起始的数最好是0或1),这是关键的点
如何通过rand7()这个函数来得到10的整数倍个的正整数呢?
function rand7(){
return rand(1,7);
}
假设最简单的情况,只需要调用两次rand7()方法,然后可以得到:
集合A{1,2,3,4,5,6,7},集合B{1,2,3,4,5,6,7},这样我们发现AB结合无法得到1,OK,将AB中的元素集体-1,得到集合A{0,1,2,3,4,5,6},B{0,1,2,3,4,5,6};
$a = rand7()-1; // 0-6
$b = rand7()-1; // 0-6
下面又是重点:
AB中元素各7个,结合A和B中元素的特点,为了能得到连续而不重复的新集合,将A中元素集体扩大7倍,得到集合C{0,7,14,21,28,35,42},此时来看一下集合B和集合C:
B{0,1,2,3,4,5,6}
C{0,7,14,21,28,35,42}
$c = $a*7 + $b; // 0-48
有没有发现:如果每次从B和C中各取一个元素出来相加,所有的可能性正好是0-48这49个连续的正整数(姑且就把0看做正整数吧),并且每个正整数出现的概率都是1/49?
接下来对新集合作出一些限制条件就可以得到10的整数倍个连续的正整数,那么问题来了:我们是选择10,20,30,还是40呢?我的选择是40(即剔除掉40-48这9个数,取0-39这40个数),因为这样程序一次就能成功返回的概率是40/49,假如你选择0-9,那么程序成功返回的概率只有10/49,怕不是要死循环?
接下来的方法就很多了,大家见仁见智,我提供两种吧
1、判断返回值
在0-9之间,-(-1)输出;
在10-19之间,-9输出;
在20-29之间,-19输出;
。。。
耿直的实现,代码比较繁琐罢了
2、这种方法其实和思路有点不符,但是应该是比较简单的实现方法
将集合D{0,1,2,3,4,5,.....39}中元素集体缩小10倍,并向下取整,得到集合E{0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3},再与A或B相加,结果在+1,得到1-10;
if($c >= 40){
//递归调用自身
return rand10();
}else{
return floor($c/10)+$b+1;
}
完整代码(法二):
<?php
function rand7(){
return rand(1,7);
}
function rand10(){
$a = rand7()-1;// 0-6
$b = rand7()-1;// 0-6
$c = $a*7 + $b;// 0-48
if($c >= 40){
//递归调用自身
return rand10();
}else{
return floor($c/10)+$b+1;
}
}
echo rand10();