核心要点

  • 构造均匀大空间:(rand7()-1)*7 + rand7() 得到 1..49 上的均匀整数。

  • 取能被 10 整除的前缀:保留 1..40,对其余 41..49 拒绝并重抽。

  • 映射:把 1..40 用 (num-1)%10 + 1 折叠到 1..10,等概率。

  • 期望调用:每轮成功概率 40/49,期望约 49/40·2 ≈ 2.45 次 rand7。

标准回答

一、核心回答可以先说核心思路:先造一个更大的均匀空间,再只保留能被 10 平均分的部分一、两次 rand7 造出 1 到 49。 令 num = (rand7() - 1) * 7 + rand7()。因为两次 rand7 独立均匀,这样可以得到 1..49 上的均匀整数二、只接受 1 到 40。 二、关键机制
40 可以被 10 整除,所以如果 num 在 1..40,就接受;如果在 41..49,就丢掉重抽。这里的关键是:被接受的 1..40 仍然是均匀的三、把 1 到 40 折叠成 1 到 10。 返回 (num - 1) % 10 + 1。因为 1..40 里每个结果 1..10 都正好出现 4 次,所以最终每个数概率都是1/10。三、面试补充可以补一句:不能直接对 49 取模,因为 49 不是 10 的倍数,会让某些结果多一次。拒绝 41..49 的目的,就是把偏差切掉。期望调用次数大概是 2 × 49/40,也就是 约 2.45 次 rand7 473

python
import random

def rand7():
    """题目给定:等概率返回 1..7"""
    return random.randint(1, 7)

def rand10():
    """等概率返回 1..10,拒绝采样保证均匀"""
    while True:
        num = (rand7() - 1) * 7 + rand7()   # 均匀落在 1..49
        if num <= 40:                        # 接受 1..40
            return (num - 1) % 10 + 1        # 折叠到 1..10
        # 41..49:拒绝,重新循环

if __name__ == '__main__':
    from collections import Counter
    cnt = Counter(rand10() for _ in range(1000000))
    for k in range(1, 11):
        print(k, round(cnt[k] / 1000000, 3))  # 应都 ≈ 0.1

常见误区

⚠️ 常见踩坑

误区一:直接对 49 取模。 49 不是 10 的倍数,直接取模会让部分数字概率更高。

误区二:把 41..49 也硬映射到 1..10。 这样等于重新引入偏差,拒绝采样就失去意义了。

追问

追问 1能否复用被拒绝的 41..49 以减少 rand7 调用?

可以。41..49 本身也带着一些均匀随机性,可以继续和新的 rand7 组合,减少平均调用次数。不过面试里通常讲出“剩余熵可以复用”这个思路就够了。

追问 2一般地,如何用 randM 实现 randN?

通用做法是:用多次 randM 拼出一个足够大的均匀空间,然后取里面最大的 N 的倍数,能整除就映射,不能整除的尾巴就拒绝重抽

🔗 相似问题

同一考点的不同问法,换着练更稳

没找到想看的面试题?把你想看的告诉我们 →

延伸学习

按主题分类的相关资源,便于系统复习