目录

  • 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。

算法:

  1. 第一步:由rand()(在下面的程序中是 random_index())函数产生Rand1()函数,Rand1()函数等概率产生0和1。

  2. 第二步:计算整数 n 的二进制表示所拥有的位数 k,k=1+log2nk = 1 +log_2nk=1+log2​n

  3. 第三步:调用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;
}

复杂度分析

  1. 时间复杂度:期望时间复杂度为 O(1),但最坏情况下会达到 O(∞)(一直被拒绝)。
  2. 空间复杂度:O(1)。
  3. 期望调用次数

这里先 int num = (rand7() - 1) * 7 + rand7(); 和上面的 do...while... 是一样的

但是这时候我们舍弃了 9 个数,舍弃的还是有点多,效率还是不高,怎么提高效率呢?那就是舍弃的数最好再少一点

法二、(更快,因为拒绝的更少)
Java

/*** The rand7() API is already defined in the parent class SolBase.* public int rand7();* @return a random integer in the range 1 to 7*/
class Solution extends SolBase {public int rand10() {while (true){int num = (rand7() - 1) * 7 + rand7();// 如果在40以内,那就直接返回if(num <= 40) return 1 + num % 10;// 说明刚才生成的在41-49之间,利用随机数再操作一遍num = (num - 40 - 1) * 7 + rand7();if(num <= 60) return 1 + num % 10;// 说明刚才生成的在61-63之间,利用随机数再操作一遍num = (num - 60 - 1) * 7 + rand7();if(num <= 20) return 1 + num % 10;}}
}

这样我们可以得到 1−21 之间的随机数,只要舍弃 1 个即可

复杂度分析

  1. 时间复杂度:期望时间复杂度为 O(1),但最坏情况下会达到 O(∞)(一直被拒绝)。
  2. 空间复杂度:O(1)。
  3. 期望调用次数
    使用类似的期望计算方法,我们可以得到调用 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]内,则重新取样

int random5()
{int val = 0;do{val = 3 * (random3() - 1) + random3();}while(val > 5);return val;
}

变形 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
如:

  1. rand3() 生成rand5() ,t=5∗i,t<3∗3,∴t=5∗1=5t = 5 * i,t < 3 * 3,∴ t = 5 * 1 = 5t=5∗i,t<3∗3,∴t=5∗1=5
  2. rand5() 生成rand7() ,t=7∗i,t<5∗5,∴t=7∗3=21t = 7 * i,t < 5 * 5,∴ t = 7 * 3 = 21t=7∗i,t<5∗5,∴t=7∗3=21
  3. rand7() 生成rand10() ,t=10∗i,t<7∗7,∴t=10∗4=40t = 10 * i,t < 7 * 7,∴ t = 10 * 4 = 40t=10∗i,t<7∗7,∴t=10∗4=40
int random_n()
{int val = 0;int t;   # t为n的最大倍数,且满足t<m*mdo{val = m * (random_m() - 1) + random_m();}while(val > t);return val;
}

变形 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,就想到用分治法

可以这样求解:

  1. 因为这里的条件给了 p 是已知的,所以把一条长度为 1 的线段,左边划分长度 p, 右边划分长度 (1-p);
  2. 再把左边的 p 划分成两段,占比分别为 p 和 (1-p)
  3. 同样再把右边的 (1-p) 划分成两段,占比分别为 p 和 (1-p)
  4. 依次递归到满足精度 e−8e^{-8}e−8 为止

参考:

  1. 01发生器产生均匀分布
  2. 等概率随机函数的实现
  3. 随机数范围扩展方法总结

概率p输出1,概率1-p输出0,等概率输出0和1 【LeetCode】470. rand7()构造rand10() 系列变形(新浪、字节面试题)相关推荐

  1. 机器学习入门(06)— 输出层多元分类、softmax 归一化指数函数以及输出层的神经元数量

    输出层所用的激活函数,要根据求解问题的性质决定.一般地, 回归问题可以使用恒等函数,也就是说会将输入按原样输出: 二元分类问题可以使用 sigmoid 函数: 多元分类问题可以使用 softmax 函 ...

  2. 【学习笔记】《概率与期望全家桶》(ACM / OI)概率与期望 / 概率论知识小结

    整理的算法模板合集: ACM模板 点我看算法全家桶系列!!! 实际上是一个全新的精炼模板整合计划 这块知识很少,但是题很难- 目录 0x00 概率 0x01 基本概念 0x02 古典概率 0x03 条 ...

  3. ROC曲线是通过样本点分类概率画出的 例如某一个sample预测为1概率为0.6 预测为0概率0.4这样画出来,此外如果曲线不是特别平滑的话,那么很可能存在过拟合的情况...

    ROC和AUC介绍以及如何计算AUC from:http://alexkong.net/2013/06/introduction-to-auc-and-roc/ ROC(Receiver Operat ...

  4. 一个数变成0的概率有多少?(记忆化搜索)

    Description 给定一个数a0, 并给出定义:序列a1,a2,a3- 1.从闭区间[0,a0]中等概率随机选择一个整数k0,令a1=a0-k0 2.得到随机数a1后,再从闭区间[0,a1]中等 ...

  5. ACMNO.21 C语言-逆序输出 输入10个数字,然后逆序输出。 输入 十个整数 输出 逆序输出,空格分开 样例输入 1 2 3 4 5 6 7 8 9 0

    题目描述 输入10个数字,然后逆序输出. 输入 十个整数 输出 逆序输出,空格分开 样例输入 1 2 3 4 5 6 7 8 9 0 样例输出 0 9 8 7 6 5 4 3 2 1 提示 数组?堆栈 ...

  6. ACMNO.16用迭代法求 。求平方根的迭代公式为: X[n+1]=1/2(X[n]+a/X[n]) 要求前后两次求出的得差的绝对值少于0.00001。 输出保留3位小数 输入 X 输出 X的

    题目描述 用迭代法求 . 求平方根的迭代公式为: X[n+1]=1/2(X[n]+a/X[n]) 要求前后两次求出的得差的绝对值少于0.00001.输出保留3位小数 输入 X 输出 X的平方根 样例输 ...

  7. C语言数组只会输出两个!_只愿与一人十指紧扣_新浪博客

    #include int main() {     int a[5]={1,3,5,7,9};     int *num[5]={&a[0],&a[1],};//只赋值两个,下面就只输 ...

  8. R语言ggplot2可视化抑制可视化网格中的竖线输出、抑制可视化网格中的横线线输出、抑制背景网格输出实战

    R语言ggplot2可视化抑制可视化网格中的竖线输出.抑制可视化网格中的横线线输出.抑制背景网格输出实战 目录

  9. 三个点在同一个半圆的概率_【国际数学竞赛】列方程求概率

    在国际数学竞赛中概率的考察往往也是古典概型和几何概型,但有些题目却找不到样本空间,只有事件的交错与转化,看着很是复杂,这里就介绍一种方法--通过事件间的关系列出方程组求出概率值.下面通过2017年AM ...

最新文章

  1. 《大话数据结构》读书笔记
  2. linux下生成guid,在Linux下生成GUID的程序,及编译错误 “uuid/uuid.h: No such file or directory” 的解决办法。...
  3. rdb和aof优势劣势
  4. tomcat(1)一个简单的web server
  5. c语言编程计算人口增长模式转变示意图,读“人口增长模式及其转变示意图”,回答下列问题。(5分)(1)图中字母代表的人口增长模式是:A____________、B____...
  6. codeforces-1201 C Maximum Median
  7. python键盘输入转换为列表_Python键盘输入转换为列表的实例
  8. 没有Where条件下group by走索引
  9. leetcode最大矩形_柱状图中的最大矩形
  10. java jlabel位置_Java Swing – JLabel位置
  11. 大数据催生智慧园区_颠覆性的大数据时代催生革命发展
  12. 内网端口映射工具之80端口映射和全端口映射及辅助发布网站应用
  13. HDU-2182 Forg 动态规划DP 题解
  14. windows下安装Bugzilla 2.18
  15. 《Java SE实战指南》06-08:枚举类型(Enumerated types)
  16. 前缀,中缀,后缀表达式
  17. Mysql内核查询成本计算
  18. git下载指定tag版本
  19. import xx_rc 问题记录
  20. Windows 7 下安装VB6.0出现错误的解决办法

热门文章

  1. python求10以内的素数_python如何求100以内的素数
  2. R语言read.table读取tsv文件
  3. java 设置其他程序焦点_设置焦点的问题
  4. 传输指令ssh,sftp,scp
  5. 逻辑思维是运用计算机科学的基础概念,简析计算思维中的思维方式及思维本质...
  6. 2021年7月23日日志
  7. 命令行 控制 易微联 wifi通断器
  8. 修改cpu控制文件init.qcom.post_boot.改调节器
  9. Windows 片断(.shs)文件
  10. 四个字节的浮点数与八个字节的整数表示范围大小的对比