问题概述
例如有一个函数 RandK()
它可以生成均匀分布在 [0, K) 的整数,问如何生成均匀分布在 [0, N)的整数
解决方案
该类型题的本质是 K进制逐位生成 + 拒绝采样 ,简单来说,若我们有一个 RandK()
, 那么我们生成一个 n位的K进制数 ,我们通过 RandK() * K^ (n-1) 逐位生成 这个 【n位K进制数】,可以得到这个 【n位k进制数】的取值范围是 [0, k^n)即 [0, k^n - 1],并且这是均匀分布的!
以 n = 3, k = 2 为例
n = 3 | n = 2 | n = 1 |
---|---|---|
Rand2() * 2 * 2 | Rand2() * 2^1 | Rand2() * 2^0 |
那么也就是说,我们通过 RandK()
可以生成 [0, k^n) 间的数,那么我们可以取其中的 [0, N) 部分,就实现了 RandN()
的功能!也就是我们 【拒绝采样】 [N, k^n) 部分的数,当生成该区间的数时,就重新生成!
不过,为了减少 RandK() 被调用次数,我们可以取一个数 Num = N * max(i) ,{i = 1,2,3…} 说白了就是取小于 k^n 的 最大的 N 的倍数。然后 拒绝采用 [Num, k^n) 部分的数
实际问题
实际上,我们用 RandK 生成 RandN 时,需要区分 K > N 还是 K < N
对于 K > N:我们尝试将 K 拆解
简单来说,我们选取一个数 k ,它满足 K % k == 0
,因为我们需要保证生成均匀分布在 [0, k) 的整数,这个 k 要比 N 小!
然后 再用 k 进行 k进制 逐位生成 + 拒绝采样即可! (注意,我们做这些是为了减少 randK 的调用次数
最简单的办法: 就是直接拒绝采样 [N, K) 的数就好了
所以 K > N 这个问题一般不会这样出… 只是考虑优化
对于 K < N : K进制逐位生成 + 拒绝采样
例题:470. 用 Rand7() 实现 Rand10()
func rand10() int {
for {
num := (rand7()-1) * 7 + (rand7() - 1) // 均匀生成 [0, 49)
if num >= 0 && num < 40 {
return num % 10 + 1
}
}
}
变形题:
有一个 RandP()
, 它有 p% 的概率生成 1, 有 (100-p)% 的概率生成 0。然后用 RandP 实现 RandN ?
其实就是通过生成 (1, 0)、(0, 1) 是等概率的来模拟 Rand2()
,然后套娃即可
func Rand2() {
for {
num1, num2 := RandP(), RandP()
if num1 + num 2 == 1 {
if num1 == 1 {
return 1
} else {
return 0
}
}
}
}
func RandN() {
...
}