概率p输出1,概率1-p输出0,等概率输出0和1 【LeetCode】470. rand7()构造rand10() 系列变形(新浪、字节面试题)
目錄
- 1. 等概率輸出0和1
- 1.1 題目描述
- 1.2 解題思路 & 代碼
- 2. 以 1/N 的概率返回 1~N 之間的數
- 3. 給定函數rand5() 構造rand7() 或 rand7()構造rand10()
- 3.1 rand5() 構造rand7()
- 3.2 【LeetCode】470. rand7() 構造rand10()
- 變形 3.1 random3() 構造 random5()
- 變形 3.2
- 變形 3.3
- 變形 3.4
- 4. 返回 (0, 1) 之間的均勻分布(字節跳動面試題)
- 參考:
這一題在我面試中遇到兩次(均為2020年7月份),新浪的比較簡單,字節的做了變形,具體看后面詳細分析。
1. 等概率輸出0和1
1.1 題目描述
有一個隨機數發生器,以概率 P 產生0,概率 (1-P) 產生 1,請問能否利用這個隨機數發生器,構造出新的發生器,以 1/2 的概率產生 0 和 1 。請寫明結論及推理過程。( 注意:這里的 p 相當于是未知的,后文會提到已知 p 的類型,解題思路是不同的 )
變形:
知隨機數生成函數f(),返回0的概率是60%,返回1的概率是40%。根據f()求隨機數函數g(),使返回0和1的概率是50%,不能用已有的隨機生成庫函數。
注意:
這里的變形毫無區別,看后續分析即可知道,核心問題都是
P(0,1)=(p)?(1?p)=P(1,0)=(1?p)?pP(0,1) = (p)*(1-p) = P(1,0) = (1-p)*pP(0,1)=(p)?(1?p)=P(1,0)=(1?p)?p
所以讓 01 的情況來模擬 0, 讓 10 的情況來模擬 1 即可,代碼一模一樣
1.2 解題思路 & 代碼
題目解答:
兩次調用該 RANDOM 函數,如果其概率為 P(x),調用2次
P(1) = p
P(0) = 1-p
P’(1) =p
P’(0) = 1-p
概率如下:
P(1,1)=p?pP(1,1) = p * pP(1,1)=p?p
P(1,0)=p?(1?p)P(1, 0) = p * (1-p)P(1,0)=p?(1?p)
P(0,1)=(1?p)?pP(0, 1) = (1-p)*pP(0,1)=(1?p)?p
P(0,0)=(1?p)?(1?p)P(0, 0) = (1-p)*(1-p)P(0,0)=(1?p)?(1?p)
很明顯,這四種情況中出現 (0,1) 和 (1,0) 的概率相等,那么把 (0,1) 看成是 0 , (1,0) 看成是 1 ,那么他們輸出的概率均為 p (1 - p),其他的情況舍棄并重新調用。這樣就得到了 0 和 1 均等生成的隨機器了。
這種解法可以推廣到n個數的情況,我們知道,取n個隨機數發生器,存在n個概率相同的獨立事件,我們只使用這n個事件就得到1/n的概率了。例如 n=3 ,有 8 中情況000,001,010,011,100,101,110,111,其中001,010,100的概率都是 p2?(1?p)p^2*(1-p)p2?(1?p)。
Python
import random class Solution:def random_index(self):# """隨機變量的概率函數"""# 參數rate為list<int># 返回概率事件的下標索引# 這是一個10%概率產生0,90%概率產生1的生成器rate = [1, 9]start = 0index = 0randnum = random.randint(1, sum(rate))for index, scope in enumerate(rate):start += scopeif randnum <= start:breakreturn indexdef Rand1(self):# 第一題:可以用原發生器周期性地產生2個數,直到生成01或者10。i1 = self.random_index()i2 = self.random_index()if i1 == 0 and i2 == 1:return 1elif i1 == 1 and i2 ==0:return 0else:return self.Rand1()if __name__ == '__main__':solution = Solution()rand1 = []for i in range(20):rand1.append(solution.Rand1())print(rand1)rand2 = []for i in range(20):rand2.append(solution.Rand2(100))print(rand2)C++
int random_0_1() {int i = RANDOM();int j = RANDOM();int result;while (true){if (i == 0 && j == 1){result = 0;break;}else if (i == 1 && j == 0){result = 1;break;}elsecontinue;}return result; }這里,等概率輸出 0 和 1 的期望調用次數 K
K=22p(1?p)=1p(1?p)K= \frac{2}{2p(1-p)}= \frac{1}{p(1-p)}K=2p(1?p)2?=p(1?p)1?
可以看出,當 p 很大或者很小的時候,這個期望值會很大,也就是調用次數會很多,只有當 p 趨近于 0.5, 調用次數最少,所以這種方法的期望調用次數的時間復雜度是比較高的!
2. 以 1/N 的概率返回 1~N 之間的數
用 位運算,因為 i 個二進制位隨機的選擇 0 或 1,可以隨機的構成 0~2i2^i2i 的數,而這些數構成了所有的組合數。因此是等概率出現的。比如:2位二進制位,這兩位可以隨機為0或1而互不影響,隨機的構成了00, 01, 10, 11,它們代表了四個數,且這四個數是等概率的。
可以通過已知隨機函數 rand() 產生等概率產生 0 和 1 的新隨機函數 Rand1(),然后調用 k(k為整數n的二進制表示的位數)次 Rand1() 函數,得到一個長度為 k 的 0 和 1 序列,以此序列所形成的整數即為 1–n 之間的數字。
注意:從產生序列得到的整數有可能大于 n ,如果大于 n 的話,則重新產生直至得到的整數不大于n。
算法:
第一步:由rand()(在下面的程序中是 random_index())函數產生Rand1()函數,Rand1()函數等概率產生0和1。
第二步:計算整數 n 的二進制表示所擁有的位數 k,k=1+log2nk = 1 +log_2nk=1+log2?n
第三步:調用k次 Rand1() 產生隨機數。
代碼中 Rand2()和 Rand3()是兩種寫法
import random class Solution:def random_index(self):# """隨機變量的概率函數"""# 參數rate為list<int># 返回概率事件的下標索引# 這是一個10%概率產生0,90%概率產生1的生成器rate = [1, 9]start = 0index = 0randnum = random.randint(1, sum(rate))for index, scope in enumerate(rate):start += scopeif randnum <= start:breakreturn indexdef Rand1(self):# 1. 等概率生成 0,1i1 = self.random_index()i2 = self.random_index()if i1 == 0 and i2 == 1:return 1elif i1 == 1 and i2 == 0:return 0else:return self.Rand1()return -1def Rand2(self, n: int):# 2. 以 1/N 的概率返回 1~N 之間的數# int(res, 2) 表示把 str 類型的二進制轉成十進制 int('101',2) == 5k = len(bin(n)[2:]) # k = 1 + np.log2(n)print("k:", k) # 10res = ''for i in range(k):res += str(self.Rand1())if int(res, 2) > n:return self.Rand2(n)else:return int(res, 2)def Rand3(self, n: int):# 3. 以 1/N 的概率返回 1~N 之間的數# (1<<i) 表示 1 * 2^iimport numpy as npresult = 0k = 1 + np.log2(n)print("k:", int(k)) # 10for i in range(0, int(k)):if self.Rand1() == 1:result |= (1<<i)if result > n:return self.Rand3(n)return resultif __name__ == '__main__':solution = Solution()rand1 = []for i in range(20):rand1.append(solution.Rand1())print(rand1)rand2 = []for i in range(10):rand2.append(solution.Rand2(999))print(rand2)rand3 = []for i in range(10):rand3.append(solution.Rand3(999))print(rand3)3. 給定函數rand5() 構造rand7() 或 rand7()構造rand10()
3.1 rand5() 構造rand7()
給定一個函數rand5(),該函數可以隨機生成1-5的整數,且生成概率一樣。現要求使用該函數構造函數rand7(),使函數rand7()可以隨機等概率的生成1-7的整數。
思路:
很多人的第一反應是利用 rand5() + rand()%3 來實現 rand7() 函數,這個方法確實可以產生1-7之間的隨機數,但是仔細想想可以發現數字生成的概率是不相等的。rand()%3 產生 0 的概率是 1/5 ,而產生 1 和 2 的概率都是 2/5 ,所以這個方法產生 6 和 7 的概率大于產生 5 的概率。
分析:
要保證rand7()在整數1-7的均勻分布,可以構造一個 1-7*n 的均勻分布的隨機整數區間(n為任何正整數)。假設x是這個1-7*n 區間上的一個隨機整數,那么x%7+1就是均勻分布在1-7區間上的整數。由于(rand5()-1)*5+rand5()可以構造出均勻分布在1-25的隨機數(原因見下面的說明),可以將 22~25 這樣的隨機數剔除掉,得到的數1-21仍然是均勻分布在1-21的,這是因為每個數都可以看成一個獨立事件。
正確的方法是利用rand5()函數生成1-25之間的數字,然后將其中的 1~21 (1~7 * n,其中 n 為任意整數,這里取了 n = 3,即 1 ~ 3*7 ) 映射成1-7,丟棄 22-25 。例如生成 (1,1),(1,2),(1,3),則看成 rand7() 中的 1 ,如果出現剩下的4種,則丟棄重新生成。
(如果是rand7 ( ) 構造rand10 ( ) ,則下面的代碼循環條件變成 while ( x > 40),因為 1 ~ 7 *7 之間滿足 10?n10 * n10?n 且最大的數是 10 * 4 == 40)
解釋:
首先 rand5()-1 得到一個離散整數集合{0,1,2,3,4},其中每個整數的出現概率都是1/5。那么 (rand5()?1)?5(rand5()-1)*5(rand5()?1)?5 得到一個離散整數集合 A = {0,5,10,15,20},其中每個整數的出現概率也都是1/5。而 rand5()rand5()rand5() 得到的集合B={1,2,3,4,5}中每個整數出現的概率也是1/5。顯然集合A和B中任何兩個元素相加可以與1- 25 之間的一個整數 一 一 對應,也就是說1-25之間的任何一個數,可以唯一確定 A 和 B 中兩個元素的一種組合方式,反過來也成立。由于 A 和 B 中元素可以看成是獨立事件,根據獨立事件的概率公式 P(AB)=P(A)P(B)P(AB)=P(A)P(B)P(AB)=P(A)P(B),得到每個組合的概率是1/5?1/5=1/251/5*1/5=1/251/5?1/5=1/25。因此(rand5()?1)?5+rand5()(rand5()-1)*5+rand5()(rand5()?1)?5+rand5()生成的整數均勻分布在1?251-251?25之間,每個數的概率都是1/251/251/25。
C++
int rand7() {int x = 0;do{x = 5 * (rand5() - 1) + rand5();}while(x > 21);return 1 + x % 7; }注:為什么用 while(x>20)而不用while(x>7)呢?原因是如果用while(x>7)則有20/25的概率需要循環 while,很有可能死循環了。
這種思想是基于,rand()產生[0,N-1],把rand()視為N進制的一位數產生器,那么可以使用 rand()*N+rand() 來產生 2 位的 N 進制數,以此類推,可以產生3位,4位,5位…的N進制數。這種按構造N進制數的方式生成的隨機數,必定能保證隨機,而相反,借助其他方式來使用rand()產生隨機數(如 rand5() + rand()%3 )都是不能保證概率平均的。
此題中N為5,因此可以使用rand5()*5+rand5()來產生2位的5進制數,范圍就是1到25。再去掉22-25,剩余的除3,以此作為rand7()的產生器。
3.2 【LeetCode】470. rand7() 構造rand10()
法一、
Java
public int rand10() {// 首先得到一個數int num = (rand7() - 1) * 7 + rand7();// 只要它還大于40,那你就給我不斷生成吧while (num > 40)num = (rand7() - 1) * 7 + rand7();// 返回結果,+1是為了解決 40%10為0的情況return 1 + num % 10; }復雜度分析
這里先 int num = (rand7() - 1) * 7 + rand7(); 和上面的 do...while... 是一樣的
但是這時候我們舍棄了 9 個數,舍棄的還是有點多,效率還是不高,怎么提高效率呢?那就是舍棄的數最好再少一點
法二、(更快,因為拒絕的更少)
Java
這樣我們可以得到 1?21 之間的隨機數,只要舍棄 1 個即可
復雜度分析
使用類似的期望計算方法,我們可以得到調用 Rand7 的期望次數約為 2.2123。
參考:leetCode 470 題解
變形 3.1 random3() 構造 random5()
已知random3()這個隨機數產生器生成[1, 3]范圍的隨機數,請用random3()構造random5()函數,生成[1, 5]的隨機數?
如何從[1-3]范圍的數構造更大范圍的數呢?同時滿足這個更大范圍的數出現概率是相同的,可以想到的運算包括兩種:加法和乘法
考慮下面的表達式:
3?(random3()–1)+random3()3 * (random3() – 1) + random3()3?(random3()–1)+random3()
可以計算得到上述表達式的范圍是[1, 9] 而且數的出現概率是相同的,即1/9
下面考慮如何從[1, 9]范圍的數生成[1, 5]的數呢?
可以想到的方法就是 rejection sampling 方法,即生成[1, 9]的隨機數,如果數的范圍不在[1, 5]內,則重新取樣
變形 3.2
給定一個函數rand()能產生0到n-1之間的等概率隨機數,問如何產生0到m-1之間等概率的隨機數?
int random(int m , int n) {int k = rand();int max = n-1;while(k < m){k = k*n + rand();max = max*n + n-1;}return k/(max/n); }變形 3.3
將這個問題進一步抽象,已知 random_m() 隨機數生成器的范圍是 [1, m] 求random_n() 生成 [1, n] 范圍的函數,m < n && n <= m *m
其中 t 為 n 的最大倍數,且滿足 t< m*m
如:
變形 3.4
如何產生如下概率的隨機數?0出1次,1出現2次,2出現3次,n-1出現n次?
int random(int size) {while(true){int m = rand(size);int n = rand(size);if(m + n < size)return m+n;} }4. 返回 (0, 1) 之間的均勻分布(字節跳動面試題)
題目描述:
有一個函數 weak_random() 能以 p 概率返回 0 , 以 1-p 概率返回 1 , 且 p 已知 , 使用weak_random 實現float_random()返回(0,1)之間的浮點數 要求均勻分布 精度為 1e-8
先回顧一下均勻分布:
均勻分布的概率密度函數為:
f(x)={1b?a,a?<?x?<?b0,elsef(x)= \begin{cases} \frac{1}{b - a}, & \text {a < x < b} \\0, & \text{else} \end{cases} f(x)={b?a1?,0,?a?<?x?<?belse?
可以理解為落在 (a, b) 區間內都是等概率的,概率均為 1b?a\frac{1}{b - a}b?a1?
解題思路:
可以理解為把前者映射到后者: (0,1) -> (T - 5e-9, T+5e-9 )
前文提到的第一種最簡單的類型,即等概率輸出 0 和 1 的期望調用次數
K=22p(1?p)=1p(1?p)K= \frac{2}{2p(1-p)}= \frac{1}{p(1-p)}K=2p(1?p)2?=p(1?p)1?
可以看出,當 p 很大或者很小的時候,這個期望值會很大,也就是調用次數會很多,只有當 p 趨近于 0.5, 調用次數最少,所以這種方法的期望調用次數的時間復雜度是比較高的!
這一題,返回 (0,1)之間均勻分布的調用次數 K 的下限:
K>?log(e?8)?plogp?(1?p)log(1?p)K>\frac{-log(e^{-8})}{-plogp - (1-p)log(1-p)}K>?plogp?(1?p)log(1?p)?log(e?8)?
這是面試官提示的,一看到帶有 log,就想到用分治法。
可以這樣求解:
參考:
總結
以上是生活随笔為你收集整理的概率p输出1,概率1-p输出0,等概率输出0和1 【LeetCode】470. rand7()构造rand10() 系列变形(新浪、字节面试题)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CAD怎么快速查看图纸文件呢?CAD怎么
- 下一篇: 一起来庆祝属于GISer的节日GIS D