[路飞]_leetcode_470.用 Rand7() 实现 Rand10()

116 阅读3分钟

给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数,试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。

你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。

每个测试用例将有一个内部参数 n,即你实现的函数 rand10() 在测试时将被调用的次数。请注意,这不是传递给 rand10() 的参数。

示例 1:

输入: 1
输出: [2]

示例 2:

输入: 2
输出: [2,8]

示例 3:

输入: 3
输出: [3,8,10]

解题思路

这一题我们可以使用拒绝采样法。如果生成的随机数满足要求,那么就返回该随机数,否则会不断生成,直到生成一个满足要求的随机数为止。 我们只需要能够满足等概率的生成 10 个不同的数即可,具体的生成方法可以有很多种,比如我们可以利用两个 Rand7() 相乘,我们只取其中等概率的 10 个不同的数的组合即可。为了提高命中率,需要多次采样处理。

我们可以先实现Rand2()到Rand4(),Rand2()取值为[1, 2],Rand4()取值为[1, 2, 3, 4],以下R2代表为Rand2()

    R2 + R2 => ? = [2, 3, 4]
    1  +  1 = 2
    1  +  2 = 3
    2  +  1 = 3
    2  +  2 = 4
以上最终的取值范围为 [2, 3, 4] 还未达到Rand4()的要求。
我们可以将第一个因子R2-1。
    R2 - 1 + R2 => ? = [1, 2, 3]
    0  +  1 = 1
    0  +  2 = 2
    1  +  1 = 2
    1  +  2 = 3
以上最终的取值范围为 [1, 2, 3] 还未达到Rand4()的要求。
从上面可以发现 1、3 的出现的概率是25%,2出现的概率是50%。可以发现第3个和第4个运算式的第一个数都为时2,可以得到结果为3,4的结果。
    (R2 - 1) * 2 + R2 => ? = [1, 2, 3, 4]
    0  +  1 = 1
    0  +  2 = 2
    2  +  1 = 3
    2  +  2 = 4
通过以上案例,最终我们将Rand2()转化成Rand4()。
从以上我们可以知道算式的数量是两个Rand的乘积,因为算式的组成是两个Rand2()枚举得到的结果。

再以Rand2()和Rand3()为例,我们知道Rand2()和Rand3()最终的到的是Rand(2 * 3)() => Rand6()。
    (R2 - 1) + R3 = [?] => R6
    0  +  1 = 1
    0  +  2 = 2
    0  +  3 = 3
    1  +  1 = 2
    1  +  2 = 3
    1  +  3 = 4
最终得到的取值范围为 [1, 2, 3, 4]。从最后一个等式可以看到当第一个因子乘于3时,也就是Rand3的最大数,等式结果为6,相对的第5个等式第一个因子乘于3,得到5
    (R2 - 1) * 3 + R3 => R6
    0  +  1 = 1
    0  +  2 = 2
    0  +  3 = 3
    3  +  1 = 4
    3  +  2 = 5
    3  +  3 = 6
结果取值范围为 [1,2,3,4,5,6] 符合Rand6()。

根据以上我么可以得出结论 (RandX - 1) * Y + RandY = Rand(X * Y)

那么如何让Rand4到Rand2呢?
我们可以很容易的知道R4对2取余加1。
    R4 % 2 + 1 = ? => R2
    1 % 2 + 1 = 2
    2 % 2 + 1 = 1
    3 % 2 + 1 = 2
    4 % 2 + 1 = 1
我们可以得到公式 Rand(X * Y) % X + 1 = RandY

代码

var rand10 = function() {
    while(true) {
        // (RandX - 1) * Y + RandY = Rand(X * Y)
        let num = (rand7() - 1) * 7 + rand7() // rand49
        //  Rand(X * Y) % X + 1 = RandY
        if (num <= 40) return num % 10 + 1

        // 提高命中率
        num = num - 40 // rand49 - rand40 = rand9
        // rand9
        num = (num - 1) * 7 + rand7() // rand63
        if (num <= 60) return num % 10 + 1

        num = num - 60 // rand63 - rand60 = rand3
        // rand3
        num = (num - 1) * 7 + rand7() // rand21
        if (num <= 20) return num % 10 + 1
        // 舍去21 重新取样
    }
};