[M数学] lc470. 用 Rand7() 实现 Rand10()(概率+拒绝采样+经典好题)

Posted Ypuyu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[M数学] lc470. 用 Rand7() 实现 Rand10()(概率+拒绝采样+经典好题)相关的知识,希望对你有一定的参考价值。

1. 题目来源

链接:470. 用 Rand7() 实现 Rand10()

高赞题解:

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之路

LC 470. Implement Rand10() Using Rand7()

[470]. 用 Rand7 实现 Rand10

[470]. 用 Rand7 实现 Rand10

470. 用 Rand7() 实现 Rand10()

LeetCode 470. 用 Rand7() 实现 Rand10()