随机数——百度面试题

题目如下:

  已知一随机发生器,产生0的概率是p,产生1的概率是1-p,现在要你构造一个发生器,使得它构造0和1的概率均为1/2;构造一个发生器,使得它构造1、2、3的概率均为1/3;...,构造一个发生器,使得它构造1、2、3、...n的概率均为1/n,要求复杂度最低。

  分析:

在这里有三个小问题,但是都可以归结到n的情况。

(1)首先针对1/2的情况,我们可以产生两位随机数,如果是00或者11则丢弃,如果是01或者10则保留,他们的概率均为p*(1-p),因此两者概率相等为1/2。

(2)对于1/3的情况,同样可以产生三位随机数,保留100、010、001,舍弃其他的,他们的概率均为p*(1-p)^2。

(3)思维扩展一下,对于生成1,2,...n的概率分别是1/n,只需要每个数的概率相等即可。自然想到产生n位随机数,保留只有一位为1的组合,舍弃其余的。虽然这样可以产生1到n的随机数,但是效率明显十分低.假设产生一位0,1的随机数需一个单位的时间,那么产生n位随机数需要o(n)时间,期望循环次数E = 1/(n/2^n) = 2^n/n次,所以时间复杂度为o(2^n)。那么怎样才能降低时间复杂度呢?自然会想到选取1的位数不止1位,假设选取x位为1,总位数为y位,那么需要满足C(x,y) >= n.并且可以计算得到这些概率为p^x*(1-p)^y.注意到题目中需要复杂度最低,因此考虑到组合数中中间值取到最大C(x/2,x),所以只需要取最小的x使得C(x/2,x) >= n就能达到最小得复杂度。

举个例子:n = 7,我们可以取到最小的x = 5,使得C(2,5) >= 7。这样共有10个组合他们的概率相等:00011,00101,00110,01001,01010,01100,10001,10010,10100,11000.

舍弃后面三个组合,就可以得到概率相等的7个组合。

   接下来就是如何对其中一个组合赋值,比如:00011对应随机数1,01010对应随机数5.其实发现其中的规律我想了有点久,最后发现了一个规律,我举两个例子吧,比如找01010对应的数,可以找最高位的1所在的位置3,然后取组合数C(2,3),接着往低位找第二个1所在位置1,取组合数C(1,1),最后把这两个数加起来再加1.

找11000对应的数,同样可以计算得到组合数:C(2,4),C(1,3),加起来为6+3+1 = 10,当然这个数大于n(7),所以被舍弃。

   在贴代码之前我再讲一个写代码过程碰到的一个很奇怪的问题,这个和我之前对Java中的Random这个类不是很熟悉有关。

   在我第一次写下面这个产生随机数0和1的方法时,每次都产生一个Random对象,导致了在循环中调用的时候产生的随机数并不随机,这个和Random使用的种子是当前系统的时间有关,可以想象在循环中如果多次调用,由于系统时间变化很小,所以产生的随机数不随机。这个问题让我检查了好几遍代码都发现不了问题,特此mark一下,大神们可以忽略,不过对于之前不怎么清楚的朋友,给个提醒吧。

开始写的产生0和1的随机函数

public static boolean random0Or1() {
		double p = 0.1;
		if (new Random().nextDouble() <= p) {
			return false;
		} else {
			return true;
		}
	}
完整代码:

public static int randomWithN(int n, Random random) {
		int x = 2, y = 1;
		while (combine(x, x / 2) < n) {
			x++;
		}
		y = x / 2;
		boolean[] a = new boolean[x];
		int count = 0;
		do {
			count = 0;
			for (int i = 0; i < a.length; i++) {
				a[i] = random0Or1(random);
				if (a[i]) {
					count++;
					if (count > y) {
						break;
					}
				}
			}
		} while (count != y);

		int sum = 0;
		for (int i = a.length - 1; i >= 0 && y != 0; i--) {
			if (a[i]) {
				sum += combine(i, y);
				y--;
			}
		}
		sum++;
		if (sum > n) {
			return randomWithN(n, random);
		}
		return sum;
	}

	public static int combine(int n, int r) {
		if (r > n) {
			return 0;
		}
		if (r > n / 2) {
			r = n - r;
		}
		double s = 0;
		for (int i = n - r + 1; i <= n; i++) {
			s += Math.log(i);
		}
		for (int i = 2; i <= r; i++) {
			s -= Math.log(i);
		}
		return (int) (Math.pow(Math.E, s) + 0.1);
	}

	public static boolean random0Or1(Random random) {
		double p = 0.1;
		if (random.nextDouble() <= p) {
			return false;
		} else {
			return true;
		}
	}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值