Part 1
已知 rand_N() 可以等概率的生成[1, N]范围的随机数
那么:
(rand_X() - 1) × Y + rand_Y() ==> 可以等概率的生成[1, X * Y]范围的随机数
即实现了 rand_XY()
简单来说就是,前半部分(rand_X-1)*Y
是为了保证与后半部分rand_Y
的每一位数字依次组合没有重叠。
举例:
rand9() : {1, 2, 3, 4, 5, 6, 7, 8, 9}
rand9()-1 : {0, 1, 2, 3, 4, 5, 6, 7, 8} // rand() 随机数列 + 一个常数,不会改变随机概率
[rand9()-1] * 7 : {0, 7, 14, 21, 28, 35, 42, 49, 56} // 这里就可以看出端倪了,前半部分数字间的间隔刚好就为7
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
令前半部分[rand9()-1] * 7 为 A,后半部分rand7() 为 B
A\B | 1 | 2 | 3 | 4 | 5 | 6 | 7
---------------------------------------
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
7 | 8 | 9 | 10| 11| 12| 13| 14
14 | .... 懒得打了
21 | ....
28 |
35 |
42 |
49 |
56 |
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
以上参考: 力扣-题解
因此,可以得到以上结论。
Part 2
反之,如果要rand4()实现rand2(),由于需要得到的数在[1,2]区间内,因此需要取余运算,再加1即可。
rand6() % 2 + 1 = ?
1 % 2 + 1 = 2
2 % 2 + 1 = 1
3 % 2 + 1 = 2
4 % 2 + 1 = 1
5 % 2 + 1 = 2
6 % 2 + 1 = 1
rand5() % 2 + 1 = ?
1 % 2 + 1 = 2
2 % 2 + 1 = 1
3 % 2 + 1 = 2
4 % 2 + 1 = 1
5 % 2 + 1 = 2
事实上,只要rand_N()
中N是2的倍数,就都可以用来实现rand2()
,反之,若N不是2的倍数,则产生的结果不是等概率的。
Part 3
要通过rand7()实现rand10(),根据Part1的推论,可以通过(rand7()-1)*7+rand7()=rand49()得到rand49(),但是49并不是10的倍数,于是需要把[41,49]的部分拒绝采样。
class Solution extends SolBase {
public int rand10() {
while(true) {
int num = (rand7() - 1) * 7 + rand7(); // 等概率生成[1,49]范围的随机数
if(num <= 40) return num % 10 + 1; // 拒绝采样,并返回[1,10]范围的随机数
}
}
}
Part 4 - 优化
在Part3中,舍弃了[41,49]的部分,如何才能减少舍弃的数字,提高命中率总而提高随机数生成效率?
class Solution extends SolBase {
public int rand10() {
while(true) {
int a = rand7();
int b = rand7();
int num = (a-1)*7 + b; // rand 49
if(num <= 40) return num % 10 + 1; // 拒绝采样
a = num - 40; // rand 9
b = rand7();
num = (a-1)*7 + b; // rand 63
if(num <= 60) return num % 10 + 1;
a = num - 60; // rand 3
b = rand7();
num = (a-1)*7 + b; // rand 21
if(num <= 20) return num % 10 + 1;
}
}
}
参考 力扣-题解