1. LeetCode 470题核心知识点解析
470题在LeetCode题库中属于中等难度题目,主要考察随机数生成与概率控制能力。题目要求实现一个rand10()函数,该函数基于rand7()生成1-10的均匀随机数。这道题看似简单,但涉及多个计算机科学核心概念。
1.1 问题本质与数学原理
这道题的核心在于理解如何通过有限范围的随机数生成器构造更大范围的均匀分布。rand7()只能生成1-7的整数,而我们需要扩展到1-10且保持均匀性。
最直观的解法是拒绝采样法(Rejection Sampling):
- 通过公式
(rand7()-1)*7 + rand7()生成1-49的均匀随机数 - 拒绝41-49的数字(因为不能被10整除)
- 对1-40的数字取模运算
x%10 + 1
关键点:确保每个输出结果的概率严格相等。1-40中每个数字出现概率都是1/49,因此最终每个结果的概率都是4/49 ≈ 1/10
1.2 优化解法与数学证明
基础解法平均需要调用2.45次rand7()(49/40*2)。更优的方案是复用被拒绝的样本:
python复制def rand10():
while True:
a = rand7()
b = rand7()
idx = (a-1)*7 + b # 1-49
if idx <= 40:
return idx % 10 + 1
# 利用剩余9个数字(41-49)
a = idx - 40 # 1-9
b = rand7()
idx = (a-1)*7 + b # 1-63
if idx <= 60:
return idx % 10 + 1
# 再利用剩余3个数字(61-63)
a = idx - 60 # 1-3
b = rand7()
idx = (a-1)*7 + b # 1-21
if idx <= 20:
return idx % 10 + 1
这个优化将平均调用次数降低到约2.21次。数学期望计算:
E = 2*(40/49) + 4*(9/49)(60/63) + 6(9/49)(3/63)(20/21) + ...
2. 同类问题扩展与解题模板
2.1 通用解法框架
对于任何"用randA()实现randB()"的问题,都可以采用以下模板:
- 找到最小的k使得A^k ≥ B
- 构造均匀分布的更大范围:
(randA()-1)*A^(k-1) + ... + (randA()-1)*A + randA() - 拒绝采样超出B*floor(A^k/B)的部分
- 对有效范围取模
2.2 典型变种题目
-
用rand5()实现rand7():
python复制def rand7(): while True: x = (rand5()-1)*5 + rand5() # 1-25 if x <= 21: # 3*7 return x % 7 + 1 -
不均匀硬币产生均匀结果:
给定不均匀硬币(P(head)=p),实现公平的coin flip:python复制def fair_coin(): while True: a = flip() # 第一次抛掷 b = flip() # 第二次抛掷 if a != b: # HT或TH return a == 'H' # HT返回True,TH返回False
3. 面试考察要点与评分标准
3.1 面试官预期回答
-
基础回答(60分):
- 能正确实现拒绝采样
- 理解均匀分布的概念
- 时间复杂度分析
-
进阶回答(80分):
- 提出优化方案(样本复用)
- 计算数学期望
- 讨论最坏情况
-
完美回答(100分):
- 给出数学证明
- 扩展到通用情况
- 讨论工业应用场景
3.2 常见错误与纠正
-
错误:直接相加取模
python复制# 错误示范 def rand10(): return (rand7() + rand7()) % 10 + 1问题:结果不是均匀分布。例如得到2的概率(1+1)高于得到14的概率(7+7)
-
错误:简单拼接数字
python复制# 错误示范 def rand10(): return int(str(rand7()) + str(rand7())) % 10 + 1问题:生成的数字范围是11-77,分布不均匀
4. 工业应用与优化技巧
4.1 实际应用场景
- 游戏开发:当需要从非均匀分布的随机源生成特定概率分布时
- AB测试:在流量有限情况下均匀分配实验组和对照组
- 密码学:生成特定范围的随机密钥
4.2 性能优化实践
-
预计算阈值:
python复制# 预先计算拒绝阈值 THRESHOLD = (A**k // B) * B def randB(): while True: x = generate_uniform(A, k) if x <= THRESHOLD: return x % B + 1 -
比特位操作优化:
当A和B都是2的幂次时,可以使用位运算加速:python复制def rand8(): # 假设rand4()返回0-3 return (rand4() << 1) | rand4() & 1 -
缓存策略:
对于昂贵的随机源(如硬件随机数生成器),可以批量生成并缓存:python复制class RandBuffer: def __init__(self, source, size): self.buffer = [source() for _ in range(size)] self.idx = 0 def get(self): if self.idx >= len(self.buffer): self.__init__(source, size) val = self.buffer[self.idx] self.idx += 1 return val
5. 测试用例设计与验证
5.1 正确性验证方法
-
卡方检验:
python复制from scipy.stats import chisquare def test_rand10(): counts = [0]*10 trials = 100000 for _ in range(trials): counts[rand10()-1] += 1 chi2, p = chisquare(counts) assert p > 0.05 # 通过显著性检验 -
可视化分布:
python复制import matplotlib.pyplot as plt plt.hist([rand10() for _ in range(10000)], bins=10) plt.show()
5.2 边界测试用例
- 极小样本测试(n=10)
- 极大样本测试(n=1e6)
- 并发环境测试(多线程调用)
- 性能压力测试(连续调用1e6次计时)
6. 进阶话题与扩展阅读
6.1 概率分布转换
-
从均匀分布到其他分布:
- 逆变换采样(Inverse Transform Sampling)
- 接受-拒绝采样(Acceptance-Rejection Sampling)
- Box-Muller变换(正态分布)
-
马尔可夫链蒙特卡洛:
当无法直接采样时,使用MCMC方法(如Metropolis-Hastings算法)
6.2 相关LeetCode题目
-
- Random Flip Matrix
-
- Random Pick with Weight
-
- Random Pick with Blacklist
-
- Generate Random Point in a Circle
在实际面试中遇到这类题目时,建议先明确问题约束条件,讨论基础解法的时间/空间复杂度,再逐步优化。我个人的经验是,对于随机数问题,先写出暴力解法确保正确性,再考虑优化通常比直接追求最优解更稳妥。
