[M数学] lc470. 用 Rand7() 实现 Rand10()(概率+拒绝采样+经典好题)
Posted Ypuyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[M数学] lc470. 用 Rand7() 实现 Rand10()(概率+拒绝采样+经典好题)相关的知识,希望对你有一定的参考价值。
1. 题目来源
高赞题解:
2. 题目解析
这种等概率生成的问题非常的经典,需要仔细考虑,严谨推导。
细节与坑点:
rand7()
可以等概率生成 1~7 之间的数,那么如果要求等概率生成 2~14 之间的数,一个直观的想法就是rand7()+rand7()
其值域即为[2,14]
之间的整数。但其实它并不是等概率的。 例如,2 只能由 1—1 组合生成,而类似于 6,可以 1—5,2—4,3—3 等多种类型组合生成方式,可大致认为每个数生成概率呈正态分布。- 10 个
rand7()
相加,和对 10 取模,也可以通过本题。虽然各个数是呈正态分布的。但是模 10 可以认为它是以个位数[0,9]
对所有数进行一个分组,那么这 10 个组中的元素个数差不多相同。在此要注意,这个差不多也不是平均的,只不过是能骗过评测机罢了。 有兴趣可以自己分组打印看看,每组有多少个数字,并将rand7()
循环 10000 次,再看看,每组有多少数字,计算一下概率。
拒绝采样:
- 参考 官方题解,评论可以看看------用 Rand7() 实现 Rand10() 。
- 我们需要等概率生成
rand10()
,那么就要尽可能找到能够等概率生成 10 的倍数的区间,然后再对 10 取模,那么就能等概率生成[1,10]
了。 - 在此有个通用的做法:
(rand_X() - 1) × Y + rand_Y()
==> 可以等概率的生成[1, X * Y]
范围的随机数。在本题操作为int t = (rand7() - 1) * 7 + rand7();
t
即为等概率生成[1,49]
区间的数。- 可以这样理解。
rand7()-1
会等概率先生成 0,7,14,21,28,35,42。然后后面的rand7()
会在其基础上等概率加上[1, 7]
。 - 第一个
rand7
为 0 时:1, 2, 3, 4, 5, 6, 7 (均为1/7概率) - 第一个
rand7
为 1 时:8, 9, 10, 11, 12, 13, 14 - 以此类推
- 第一个
rand7
为 7 时:43, 44, 45, 46, 47, 48, 49 (均为1/7概率) - 两者相乘,
[1, 49]
每个数出现的概率都是 1/49,为等概率生成。
- 可以这样理解。
- 我们仅需要等概率生成
[1,40]
这 40 个数,然后对 10 取模,将其映射到 1~10 即可。所以,[41,49]
是不需要的,那么当我们生成的t
在[41,49]
这个范围内,我们要进行 拒接采样。 即,这次采样是无用的,递归调用rand10()
,另其重新生成一个[1,49]
区间的数,还是仅取[1,40]
之间的数,由于是等概率的。所以不论取多少次,[1,40]
这个区间的数,一定是等概率产生。 - 考虑,我们取一次
t
,产生[41,49]
的无用数的概率是9/49
,有效采样概率是40/49
,期间调用了 2 次rand7()
。一个事件产生的概率是1/p
,那么得操作p
次,才能保证该事件发生。在此,就需要操作49/40
次才能保证取到[1,40]
区间的数,每次调用 2 次rand7()
,即调用radn7()
的次数是49/20
次。 - 也就是拒绝采样的范围越少,我们调用
rand7()
的次数也就越少,整个算法复杂度就越低。 所以在此选择[1,40]
区间,而不是[1, 10]、[1, 20]
等区间,后者拒绝样本的范围太大,导致rand10()
调用次数增加,导致rand7()
调用次数增加。 - 在此可以理解为,如果生成了
[41,49]
之间的数,那么这次rand10()
就作废掉了,顺带着本次的两次rand7()
也作废掉了。但影响[1,40]
之间的等概率生成吗?并不影响。 因为每次生成t
,[1,40]
的内部,都是等概率的生成的,不论对t
采样多少次,其也均为等概率生成。
有关于拒绝采样,也可以看看 力荐-----从最基础的讲起如何做到均匀的生成随机数。
、力荐-----从抛硬币开始,循序渐进把这道题吃透!看不懂算我输!其中讲的非常不错。
最后一个技巧,将 [1,n]
之间的数,映射到 [1,k]
。可以 (n-1)%k+1
,但貌似没太大意义。 也可以直接 n%k+1
按照余数分,{1,11,21,31}
在一组,{10,20,30,40}
在一组,%k
仅能生成 [0,k-1]
之间的数,故最后再 +1
做一个映射,映射到 [1,k]
即可。
- 时间复杂度:期望 O ( 1 ) O(1) O(1),最坏 O ( 无 穷 ) O(无穷) O(无穷),即采样一直被拒绝的情况。
- 空间复杂度: O ( 1 ) O(1) O(1)
// The rand7() API is already defined for you.
// int rand7();
// @return a random integer in the range 1 to 7
class Solution {
public:
int rand10() {
int t = (rand7() - 1) * 7 + rand7();
if (t > 40) return rand10(); // 拒绝采样
// 两种映射方式,均可。前者从 0 开始,后者从 1 开始
// return (t - 1) % 10 + 1;
return t % 10 + 1; // 将 1~n 的数,映射为 1~k 的数
}
};
以上是关于[M数学] lc470. 用 Rand7() 实现 Rand10()(概率+拒绝采样+经典好题)的主要内容,如果未能解决你的问题,请参考以下文章
LeetCode 470 用Rand7()实现Rand10()[数学] HERODING的LeetCode之路