LeetCode 470 用 Rand7() 实现 Rand10()


文章目录

摘要

LeetCode 470 这道题乍一看像是"随机数题",但真正考的并不是 API 调用,而是**对概率、均匀分布以及拒绝采样(Rejection Sampling)**的理解。

题目限制只能用 rand7(),却要生成 rand10(),这类问题在工程里并不少见,比如基于受限随机源做更高维度的随机模拟、灰度实验、AB 测试分流等。

这篇文章会从直觉误区开始,一步步推到正确解法,并用 Swift 写出一个可以直接跑的 Demo,顺便聊聊它在真实系统里的意义。

描述

题目给了一个现成的方法:

text 复制代码
rand7() -> 返回 [1, 7] 之间的均匀随机整数

我们的目标是实现:

text 复制代码
rand10() -> 返回 [1, 10] 之间的均匀随机整数

限制条件很严格:

  • 只能调用 rand7()
  • 不能使用系统随机函数
  • 要求结果是均匀分布

很多人第一反应是:

"那我 rand7() % 10 + 1 不就行了?"

这个想法非常自然,但也是这道题的第一个坑。

因为 7 不能整除 10,直接取模一定会导致某些数字出现的概率更高。

所以问题的本质其实是:

如何把一个有限、离散、范围不整除的随机源,映射成另一个均匀随机源?

题解答案

核心思路只有一句话:

rand7() 组合出一个更大的均匀空间,再从中"筛选"出 [1,10]

具体做法是:

  1. 用两次 rand7() 生成一个 [1,49] 的均匀随机数
  2. 只保留 [1,40] 这一段
  3. 对 10 取模,映射到 [1,10]

为什么是 40?

因为 40小于等于 49 的最大、能被 10 整除的数

题解代码分析

下面是一个完整、可运行的 Swift Demo ,包含 rand7() 的模拟实现,方便本地测试。

swift 复制代码
class Solution {

    // 模拟题目给定的 rand7()
    func rand7() -> Int {
        return Int.random(in: 1...7)
    }

    // 目标函数
    func rand10() -> Int {
        while true {
            // 将 rand7() 扩展成 [1, 49]
            let num = (rand7() - 1) * 7 + rand7()
            
            // 只接受 [1, 40]
            if num <= 40 {
                return (num - 1) % 10 + 1
            }
            // 超过 40 的部分直接丢弃,重新来
        }
    }
}

下面我们一行一行拆解这段逻辑。

第一步:为什么是 (rand7() - 1) * 7 + rand7()

你可以把它想象成一个二维表:

  • 第一次 rand7() 决定"行"
  • 第二次 rand7() 决定"列"

这样一来:

  • 一共 7 × 7 = 49 种组合
  • 每一种组合出现的概率完全相等

结果自然就是一个 均匀分布的 [1,49]

第二步:为什么只取 [1,40]

这是这道题最关键的地方。

  • 49 不能整除 10
  • 40 可以整除 10

如果我们只使用 [1,40],那么:

  • 每个数字 1~10 都对应 4 个等概率来源
  • 映射后仍然是均匀分布

剩下的 [41,49] 呢?

答案是:
不要了,直接重来。

这就是经典的 拒绝采样(Rejection Sampling)

第三步:为什么不会死循环

很多人担心:

"一直 reject,会不会一直跑不出来?"

实际上不会。

  • 接受概率是 40 / 49
  • 平均不到 1.25 次循环就能成功一次

在进阶问题里,这正是题目希望你分析的"期望调用次数"。

示例测试及结果

我们可以简单写个测试,看看分布是不是均匀的。

swift 复制代码
let solution = Solution()
var counter = Array(repeating: 0, count: 10)

let times = 100_000
for _ in 0..<times {
    let value = solution.rand10()
    counter[value - 1] += 1
}

for i in 0..<10 {
    print("Number \(i + 1): \(counter[i])")
}

你会看到类似这样的输出(每次略有波动):

text 复制代码
Number 1: 10021
Number 2: 9958
Number 3: 10044
Number 4: 10003
Number 5: 10012
Number 6: 9987
Number 7: 10031
Number 8: 9996
Number 9: 10019
Number 10: 9929

从结果上看,每个数字的出现次数都非常接近,说明分布是均匀的。

时间复杂度

单次 rand10()

  • 平均调用 rand7() 的次数是常数级
  • 接受概率是 40 / 49

所以:

  • 时间复杂度是 O(1)(期望意义下)

这也是为什么这种"看起来有循环"的写法,在随机问题中是完全可以接受的。

空间复杂度

  • 没有使用额外的数据结构
  • 只用了几个临时变量

所以:

  • 空间复杂度是 O(1)

总结

LeetCode 470 表面上是一个随机数题,实际上考的是三件事:

  1. 你是否真正理解"均匀分布"
  2. 你能不能跳出"取模"的直觉陷阱
  3. 你是否知道如何通过拒绝采样构造新概率空间

在真实业务里,这类思路经常用在:

  • 灰度发布中的流量均匀分桶
  • 基于有限随机源的模拟系统
  • 游戏、推荐系统里的概率控制

如果你能把这道题讲清楚,说明你对"概率 + 工程实现"这件事,已经不是停留在表面了。

相关推荐
闻缺陷则喜何志丹10 小时前
【图论 DFS 换根法】3772. 子图的最大得分|2235
c++·算法·深度优先·力扣·图论·换根法
一只大侠的侠10 小时前
Python实现TTAO算法:优化神经网络中的时序预测任务
python·神经网络·算法
千金裘换酒18 小时前
LeetCode 移动零元素 快慢指针
算法·leetcode·职场和发展
wm104318 小时前
机器学习第二讲 KNN算法
人工智能·算法·机器学习
NAGNIP18 小时前
一文搞懂机器学习线性代数基础知识!
算法
NAGNIP18 小时前
机器学习入门概述一览
算法
iuu_star19 小时前
C语言数据结构-顺序查找、折半查找
c语言·数据结构·算法
Yzzz-F19 小时前
P1558 色板游戏 [线段树 + 二进制状态压缩 + 懒标记区间重置]
算法
漫随流水19 小时前
leetcode算法(515.在每个树行中找最大值)
数据结构·算法·leetcode·二叉树