LeetCode 470. 用 Rand7() 实现 Rand10()(中)
如何进行概率拼凑以及概率转换?
Question
已有方法 rand7 可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10 生成 1 到 10 范围内的均匀随机整数。不要使用系统的 Math.random() 方法。
示例 1: 1
2输入: 1
输出: [7]1
2输入: 2
输出: [8,4]1
2输入: 3
输出: [8,1,10]
进阶: rand7()调用次数的 期望值 是多少 ? 你能否尽量少调用 rand7() ?
Intuition
题目无非就是凑出1/10的概率出来,而现在我们只有1/7的概率,可以利用乘法原则1/10=1/5 * 1/2。 因为是随机的,所以每一次的选择都不会影响下一次的概率分布,所以代码中凑出1/5和1/2的方式是可行的。
Code
1 |
|
Extension
已有概率函数 Rand5()
能够产生0-4的随机整数,如何获得 Rand7()
生成0-6的随机整数 ?
rand5可以随机生成1,2,3,4,5;rand7可以随机生成1,2,3,4,5,6,7。 rand5并不能直接产生6,7,所以直接用rand5去实现函数rand7似乎不太好入手。 如果反过来呢?给你rand7,让你实现rand5,这个好实现吗?
通用概念
一个非常直观的想法就是不断地调用rand7,直到它产生1到5之间的数,然后返回。 代码如下:
1 |
|
那么这个 rand5
的生成概率是1到5呢?上面的函数中有个while循环,只要没生成1到5间的数就会一直执行下去。 因此,我们要的1可能是第一次调用Rand7时产生,也可能是第二次,第三次,…第n次。 第1次就生成1,概率是1/7;第2次生成1,说明第1次没生成1到5间的数而生成了6,7, 所以概率是(2/7)*(1/7),依次类推。生成1的概率计算如下:
1 |
|
\(1 + \frac{2}{7} + (\frac{2}{7})^2 + ... + (\frac{2}{7})^n\) 是一个 \(a_1=1\),\(q=\frac{2}{7}\) 的等比数列。等比数列前 \(n\) 项计算公式如下: \[ S_n = \frac{a_1 \times (1-q^n)}{1-q} (q \neq 1) \] 所以: \[ 1 + \frac{2}{7} + (\frac{2}{7})^2 + ... + (\frac{2}{7})^n = \frac{1 \times (1-\frac{2}{7}^n)}{1-\frac{2}{7}} = \frac{1}{\frac{5}{7}} =\frac{7}{5} \] 因此:
1 |
|
上述计算说明Rand5是等概率地生成1,2,3,4,5的(1/5的概率)。从上面的分析中, 我们可以得到一个一般的结论,如果 M > N
,那么一定可以用 Rand_M
去实现 Rand_N
。其中, Rand_M
表示等概率生成1到M的函数,Rand_N
表示等概率生成1到N的函数。代码如下:
1 |
|
从 Rnad5
到 Rand7
回到正题,现在题目要求我们要用Rand5来实现Rand7,只要我们将Rand5 映射到一个能产生更大随机数的Rand_A,其中A > 7,就可以套用上面的模板,实现由Rnad5 到 Rnad_A,再到Rand7了。 这里要注意一点的是,你映射后的Randa一定是要满足等概率生成1到a的。比如,5 * (Rand5() - 1) + Rand5()
,Rand5产生1到5的数,减1就产生0到4的数,乘以5后可以产生的数是:0,5,10,15,20。 再加上第二个Rand5()产生的1,2,3,4,5。我们可以得到1到25, 而且每个数都只由一种组合得到,即上述代码可以等概率地生成1到25。且概率为 1/25
。
套用上面的模板,我们可以得到如下代码:
1 |
|
上面的代码有什么问题呢?可能while循环要进行很多次才能返回。 因为Rand25会产生1到25的数,而只有1到7时才跳出while循环, 生成大部分的数都舍弃掉了。这样的实现明显不好。我们应该让舍弃的数尽量少, 于是我们可以修改while中的判断条件,让x与最接近25且小于25的7的倍数相比。 于是判断条件可改为x > 21,于是x的取值就是1到21。 我们再通过取模运算把它映射到1-7即可。代码如下: 1
2
3
4
5
6
7
8def rand25():
return 5 * (rand5() - 1) + rand5()
def rand7():
x = 2**32
while x > 21:
x = rand25() # 想到这个很重要
return x % 7 + 1
参考链接
Summary
从 Rand_m
到 Rand_n
:
如果
m > n
(从大到小),套用模板,直接解决问题。1
2
3
4
5def rand_N():
x = 2 **31
while x > N: # 根据实际情况,这个条件还可以优化。
x = rand_M()
return x如果
m < n
(从小到大),用Rand_m
构造Rand_x = m * (rand_m() – 1) + Rand_m()
,如果x < n
,那就再次构造Randa_x = a * ( Rand_x – 1) + Rand_x
,直到x > n
。然后,再套用模板,直接解决问题。1
2
3
4
5
6
7
8def rand_x:
return m * (rand_m() – 1) + Rand_m() # 可以多次重复,直到x>n。
def rand_N():
x = 2 **31
while x > N: # 根据实际情况,这个条件还可以优化。
x = rand_x()
return x
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!