给定方法 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 重新取样
}
};