LeetCode 470. 用 Rand7() 实现 Rand10() 进制转换题解,解决一道等概率类型的代表题。
题目
470. 用 Rand7() 实现 Rand10()
https://leetcode.cn/problems/implement-rand10-using-rand7/
给定方法 rand7 可生成 [1,7] 范围内的均匀随机整数,试写一个方法 rand10 生成 [1,10] 范围内的均匀随机整数。
你只能调用 rand7() 且不能调用其他方法。请不要使用系统的 Math.random() 方法。
每个测试用例将有一个内部参数 n,即你实现的函数 rand10() 在测试时将被调用的次数。请注意,这不是传递给 rand10() 的参数。
二进制转换
思路
由题干可得:rand7
可随机等概率生成 [1,7]
的整数。
求一个可同样等概率生成 [1,10]
整数的函数。
一次调用rand7
中,无法生成[8,10]
。
尝试多次调用、组合生成的值
比如,rand7 + rand7
,我们看看取值分布:
取值并非等概率,并且无法通过分类来区分各个和的含义。
按二进制位生成值
上面的思路行不通的原因是,求和取值分布不均匀。
我又产生了新的思路:
- 将
rand7
改成只生成0/1
二进制值的函数; - 然后组合二进制的 0、1、2、3位,生成一个
[0,2^4]
等概率的区间; - 对二进制转成十进制表示的
[0,2^4]
区间,我们舍弃[1,10]
区间外的值;
这种组合生成值的方式,由于每次调用生成的值含义不同(位不同),因此值的分布是均匀的。
代码
|
|
复杂度分析
时间
期望复杂度为 O(1)
,最坏情况下为 O(∞)
。
空间
无额外空间占用,空间复杂度O(1)
。
七进制转换
思路
在二进制转换中,过程有些绕。
实际上,我们可以直接用rand7 -1
表示七进制生成一位的值。然后使用七进制转成十进制即可,如此一来,只需(rand7() - 1) * 7 + rand7() - 1
,rand7
单次少调用了一些,代码上也更加简洁。
代码
|
|
进阶问题优化
你能否尽量少调用
rand7()
?
上述我们的实现中,每次rand7()
组合调用的值,如果不在[1,10]
,我们进行了重试,极端情况下,rand7()
会调用多次。
实际上,我们可以将组合出来的值进行区间映射:
- 针对二进制转换,我们使用
(epBaseRand7() << 4) + (epBaseRand7() << 3) + (epBaseRand7() << 2) + (epBaseRand7() << 1) + epBaseRand7()
来生成[0,32]
,其中[0,29] + 1
均表示为[1,10]
的倍数区间,如此一来,我们只需抛弃等概率分布下的[30,32]
取值; - 针对七进制转换,同样使用
(rand7() - 1) * 7 + rand7() - 1
生成[0,49]
,其中[0,39] + 1
均表示为[1,10]
的倍数区间;
通过区间映射,我们可以提高实际运行中生成随机数的利用率。
代码
|
|