算法

在网上看到一道有意思的算法题
 

题目如下:

已知有个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();
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值