1. 程式人生 > >隨機數——百度面試題

隨機數——百度面試題

題目如下:

  已知一隨機發生器,產生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;
		}
	}