难度中等243
已有方法 rand7
可生成 1 到 7 范围内的均匀随机整数,试写一个方法 rand10
生成 1 到 10 范围内的均匀随机整数。
不要使用系统的 Math.random()
方法。
示例 1:
输入: 1 输出: [7]
示例 2:
输入: 2 输出: [8,4]
示例 3:
输入: 3 输出: [8,1,10]
提示:
rand7
已定义。- 传入参数:
n
表示rand10
的调用次数。
进阶:
rand7()
调用次数的 期望值 是多少 ?- 你能否尽量少调用
rand7()
?
思路:首先rand7()是[1,7]的均匀采样,题目要求我们给出的是[1,10]的均匀采样,但是这个目标域是大于[1,7]的,因此我们必须扩展原有的采样域,至少不能小于[1,10]。
尝试1:rand7() + rand7()
rand7 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | x |
rand7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | x |
res=rand7+rand7 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | x |
统计一下结果:
res | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | x |
p | 1/49 | 2/49 | 3/49 | 4/49 | 5/49 | 6/49 | 7/49 | 6/49 | 5/49 | 4/49 | 3/49 | 2/49 | 1/49 | x |
由于本身不具备10个拥有相同概率的结果,因此我们看看通过模10规整到[1,10]区间的样子:
res | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | x |
p | 4/49 | 4/49 | 4/49 | 4/49 | 4/49 | 5/49 | 6/49 | 7/49 | 6/49 | 5/49 | x |
发现概率并不均等,且没有办法剔除,因为res=6、7、8、9、10的这些情况只有一次,但是概率已经偏大。因此rand7()+rand7()不是一个好方案。
尝试2:rand7() * rand7()
rand7 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 5 | 5 | 5 | 5 | 5 | 5 | 5 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 7 | 7 | 7 | 7 | 7 | 7 | 7 | x |
rand7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | x |
res | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 3 | 6 | 9 | 12 | 15 | 18 | 21 | 4 | 8 | 12 | 16 | 20 | 24 | 28 | 5 | 10 | 15 | 20 | 25 | 30 | 35 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 7 | 14 | 21 | 28 | 35 | 42 | 49 | x |
统计后发现[1,10]的出现概率并不一致,即使模10也不行。
尝试3:由于乘法和加法对于域[1,49]不是全覆盖的,所以概率不均等,我们需要一种方案能够让生成的数铺满域[1,49],于是显然可以用公式 (rand7()-1) * 7 + rand7()。
此时我们可以只取前40个数,对于每个数i(i∈[1,10]),其概率都是4/49,当数落在[41,49]时,我们可以再做一次生成。
rand7()\rand(7) | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
2 | 8 | 9 | 10 | 1 | 2 | 3 | 4 |
3 | 5 | 6 | 7 | 8 | 9 | 10 | 1 |
4 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
5 | 9 | 10 | 1 | 2 | 3 | 4 | 5 |
6 | 6 | 7 | 8 | 9 | 10 | 1 | 2 |
7 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
注:表格中的内容是规整到[1,10]后的结果,由于41~49区间中的元素不足10个,因此若结果落在该区间则舍去,以保证概率。
class Solution {
public:
int rand10() {
int row, col, num;
do{
row = rand7();
col = rand7();
num = (row - 1) * 7 + col;
} while(num > 40);
return num % 10 + 1;
}
};
优化:原算法中,我们每一次被拒绝后都需要重新调用两次rand7(),现在我们看看能否复用上一次的结果。
A.[1,49],舍去[41,49],即可以得到多于区间[1,9],此时调用一次rand7(),结合[1,9]可获得[1,63]
2.[1,63],舍去[61,63],即可以得到多于区间[1,3],此时调用一次rand7(),结合[1,3]可获得[1,21]
3.[1,21],舍去[21,21],即可以得到多于区间[1,1],此时调用一次rand7(),结合[1,1]可获得[1,7],但已经不再含有[8,10],因此不用考虑。
class Solution {
public:
int rand10() {
int row, col, num;
while(true){
row = rand7();
col = rand7();
num = (row - 1) * 7 + col;
if(num <= 40){
return num % 10 + 1;
}else{
num = num - 40;
row = rand7();
num = (num - 1) * 7 + row;
if(num <= 60){
return num % 10 + 1;
}else{
num = num - 60;
row = rand7();
num = (num - 1) * 7 + row;
if(num <= 20){
return num % 10 + 1;
}
}
}
}
}
};