目录

题目描述

470. 用 Rand7() 实现 Rand10()

题意分析

给定一个黑盒 rand7(),它等概率返回 1..7 中的整数;要求实现 rand10()等概率返回 1..10 中的整数。

「等概率」是硬性要求:1..10 每个数出现的概率必须严格等于 1/10,「差不多均匀」不算对——评测和面试官都会从分布上验证。

唯一可用的随机源就是 rand7(),不能调用语言自带的随机函数;除此之外调用次数不限,题目只是鼓励尽量少调用。

难点在于 710 互质:单次调用只有 7 种等概率结果,无论套什么确定性变换都造不出 10 种等概率结果,所以必须组合多次调用,并想清楚组合之后的分布长什么样。

解法:拒绝采样构造等概率空间

核心思路

两次独立的 rand7() 可以编码出 0..4849 个等概率状态:value = (rand7() - 1) * 7 + rand7() - 1

49 不能被 10 整除,因此只接受 0..39,再用 value % 10 + 1 映射到 1..10。每个结果恰好对应 4 个状态;落入 40..48 时重新采样。

解题步骤

  • 调用两次 rand7(),按七进制位组合成 0..48 的均匀随机数。
  • 若结果小于 40,返回 value % 10 + 1
  • 否则丢弃本轮结果并重新采样。

代码实现

class Solution extends SolBase {
    public int rand10() {
        while (true) {
            int value = (rand7() - 1) * 7 + rand7() - 1;
            if (value < 40) {
                return value % 10 + 1;
            }
        }
    }
}
func rand10() int {
    for {
        value := (rand7()-1)*7 + rand7() - 1
        if value < 40 {
            return value%10 + 1
        }
    }
}

复杂度分析

  • 时间复杂度:期望 $O(1)$,每轮接受概率为 40/49,期望调用 rand7() 的次数为 2 × 49/40 = 2.45;理论最坏次数无上界。
  • 空间复杂度:$O(1)$。

关键点总结

  • 进制编码保持有序对与随机状态一一对应,因此仍然均匀。
  • 接受区间的状态数必须是 10 的倍数,才能等分给十个结果。
  • 拒绝采样保证严格等概率,不能为保证固定次数而强行映射剩余状态。

易错点总结

  • 将两次结果相加会得到三角分布,并非均匀分布。
  • 对全部 49 个状态直接取模,会让部分结果多对应一个状态。
  • 使用零基编号时应接受 value < 40,写成 <= 40 会纳入 41 个状态。
  • 被拒绝后必须重新生成完整的有序对,不能只重掷其中一次。

相似题目

题目 难度 考察点
478. 在圆内随机生成点 中等 几何区域上的拒绝采样
528. 按权重随机选择 中等 前缀和加二分把均匀随机变成加权随机
384. 打乱数组 中等 Fisher–Yates 洗牌保证排列等概率
382. 链表随机节点 中等 蓄水池抽样应对未知长度的数据流
398. 随机数索引 中等 蓄水池抽样在重复元素索引上的应用