量化面试绿皮书:34. 不公平硬币的公平赔率

文中内容仅限技术学习与代码实践参考,市场存在不确定性,技术分析需谨慎验证,不构成任何投资建议。

34. 不公平硬币的公平赔率

如果你有一枚不公平的硬币,它可能以未知的概率偏向正面或反面,

Q: 那么你能用这枚硬币产生公平的赔率吗?

A: 是的,你可以使用这枚不公平的硬币(以未知概率偏向正面或反面)来产生公平的赔率(即50-50的机会)。这可以通过一种称为冯·诺依曼抛硬币法(von Neumann’s coin flipping)的简单算法来实现。该方法利用了硬币抛掷的独立性(假设每次抛掷是独立的),并通过过滤掉某些结果来消除偏差。

方法步骤

  1. 抛掷硬币两次,记录结果。
  2. 比较两次结果
    • 如果两次结果相同(都是正面或都是反面),则忽略这组结果,并回到步骤1重新开始。
    • 如果两次结果不同(一次正面、一次反面),则取第一次抛掷的结果作为输出(例如,如果第一次是正面,则输出“正面”;如果第一次是反面,则输出“反面”)。

为什么这能产生公平的赔率?

  • 假设硬币正面出现的概率为 p p p(未知,且 0 < p < 1 0 < p < 1 0<p<1),反面出现的概率为 q = 1 − p q = 1 - p q=1p

  • 抛掷两次的可能结果和概率:

    • 两次正面:概率 p × p = p 2 p \times p = p^2 p×p=p2
    • 两次反面:概率 q × q = q 2 q \times q = q^2 q×q=q2
    • 第一次正面、第二次反面:概率 p × q p \times q p×q
    • 第一次反面、第二次正面:概率 q × p q \times p q×p
  • 当我们只接受“两次结果不同”的情况(即一正一反)时,这种结果的概率为 2 × p × q 2 \times p \times q 2×p×q(因为有两种顺序:先正后反或先反后正)。

  • 在“两次结果不同”的条件下:

    • 输出正面的概率(即第一次正面、第二次反面的情况)为:

    p × q 2 × p × q = 1 2 \frac{p \times q}{2 \times p \times q} = \frac{1}{2} 2×p×qp×q=21

    • 输出反面的概率(即第一次反面、第二次正面的情况)为:

    q × p 2 × p × q = 1 2 \frac{q \times p}{2 \times p \times q} = \frac{1}{2} 2×p×qq×p=21

  • 因此,无论 p p p 的值如何(只要 p p p 不是 0 或 1),输出正面的概率始终是 50%,输出反面的概率也是 50%。这实现了公平的赔率。

注意事项

  • 效率问题:由于硬币可能严重偏向某一面(例如, p p p 接近 0 或 1),导致“两次结果不同”的情况发生概率较低( 2 × p × q 2 \times p \times q 2×p×q 的最大值为 0.5,当 p = 0.5 p = 0.5 p=0.5 时;最小接近 0,当 p p p 接近 0 或 1 时)。因此,你可能需要多次重复抛掷才能得到一个有效输出,但平均而言,这个方法在概率上是公平的。
  • 边界情况:如果硬币完全偏向一面( p = 0 p = 0 p=0 p = 1 p = 1 p=1),则永远无法得到“两次结果不同”的结果,方法失效。但问题中描述“可能以未知的概率偏向”,隐含了硬币不是绝对确定的(即 0 < p < 1 0 < p < 1 0<p<1),所以这种情况在理论上可忽略。
  • 实际应用:这种方法常用于随机数生成或赌博场景中,当只有有偏的随机源可用时,可以“提取”公平的随机比特。

示例

  • 假设硬币严重偏向正面( p = 0.9 , q = 0.1 p = 0.9, q = 0.1 p=0.9,q=0.1)。
    • 抛掷两次:可能得到(正,正)、(正,反)、(反,正)、(反,反)。
    • 如果得到(正,反),则输出“正面”。
    • 如果得到(反,正),则输出“反面”。
    • 如果得到(正,正)或(反,反),则重试。
    • 尽管(正,正)的概率很高(81%),但一旦接受输出,正反面的概率各为 50%。

Python 实现

以下是通过 Python 实现冯·诺依曼抛硬币法的代码:

import random
from typing import Callable, Optional


def make_biased_coin(p: float) -> Callable[[], int]:
    """创建一个有偏硬币的抛掷函数。

    该函数返回一个无参数函数,每次调用模拟一次硬币抛掷。

    Args:
        p (float): 硬币出现正面的概率 (0 < p < 1)

    Raises:
        ValueError: 如果概率 p 不在 (0, 1) 范围内

    Returns:
        Callable[[], int]: 返回一个函数,调用时返回:
            1 表示正面
            0 表示反面
    """
    if not 0 < p < 1:
        raise ValueError("概率 p 必须在 (0, 1) 范围内")

    def biased_coin() -> int:
        """单次有偏硬币抛掷"""
        return 1 if random.random() < p else 0  # 1=正面, 0=反面

    return biased_coin


def fair_coin_flip(
    biased_coin: Callable[[], int], max_attempts: Optional[int] = None
) -> int:
    """使用有偏硬币生成公平结果 (冯·诺依曼方法)

    通过抛掷两次硬币并比较结果:
        - 如果相同则丢弃重试
        - 如果不同则取第一次结果

    Args:
        biased_coin (Callable[[], int]): 有偏硬币抛掷函数,返回 0 或 1
        max_attempts (Optional[int], optional): 最大尝试次数 (防止无限循环),None 表示无限制. Defaults to None.

    Raises:
        RuntimeError: 当达到最大尝试次数仍未获得有效结果时

    Returns:
        int: 公平结果: 0 (反面) 或 1 (正面)
    """
    attempts = 0

    while max_attempts is None or attempts < max_attempts:
        # 抛掷硬币两次
        first = biased_coin()
        second = biased_coin()
        attempts += 1

        # 仅在不同结果时返回第一次抛掷结果
        if first != second:
            return first

    # 达到最大尝试次数仍未成功
    raise RuntimeError(
        f"在 {max_attempts} 次尝试后仍未获得有效结果。"
        "硬币可能完全偏向某一面 (p=0 或 p=1)。"
    )


def test_fairness(p: float = 0.7, trials: int = 10_000) -> None:
    """测试公平性:统计输出结果的分布

    Args:
        p (float, optional): 有偏硬币的正面向上的概率. Defaults to 0.7.
        trials (int, optional): 总测试次数. Defaults to 10_000.
    """
    # 创建有偏硬币
    biased_coin = make_biased_coin(p)

    # 收集公平结果
    results = []
    for _ in range(trials):
        results.append(fair_coin_flip(biased_coin))

    # 计算统计结果
    heads_count = sum(results)
    tails_count = trials - heads_count

    # 输出统计信息
    print(f"\n测试配置: 硬币偏向 p={p}, 总试验次数={trials:,}")
    print(f"正面结果: {heads_count} ({heads_count/trials:.2%})")
    print(f"反面结果: {tails_count} ({tails_count/trials:.2%})")


# 创建严重偏向正面的硬币 (p=0.9)
biased_coin = make_biased_coin(p=0.9)

# 生成10个公平结果
print("生成10个公平结果:")
for i in range(1, 11):
    result = fair_coin_flip(biased_coin)
    print(f"试验 {i}: {'正面' if result == 1 else '反面'}")

# 运行统计测试 (p=0.9, 10万次试验)
test_fairness(p=0.9, trials=100_000)

代码说明

  1. 核心组件

    • make_biased_coin():创建有偏硬币生成器
    • fair_coin_flip():实现冯·诺依曼公平化算法
    • test_fairness():验证结果分布的公平性
  2. 冯·诺依曼算法实现

    while True:
        first = coin()  # 第一次抛掷
        second = coin() # 第二次抛掷
      
        if first != second:  # 仅当结果不同时
            return first    # 返回第一次结果
    
  3. 安全机制

    • 概率验证 (0 < p < 1)
    • 最大尝试次数限制 (防止 p=0 或 p=1 时的无限循环)
    • 明确的错误类型提示

输出:

生成10个公平结果:
试验 1: 反面
试验 2: 正面
试验 3: 正面
试验 4: 反面
试验 5: 正面
试验 6: 正面
试验 7: 反面
试验 8: 反面
试验 9: 反面
试验 10: 正面

测试配置: 硬币偏向 p=0.9, 总试验次数=100,000
正面结果: 50068 (50.07%)
反面结果: 49932 (49.93%)

关键特性

  1. 数学公平性证明

    • 当两次结果不同时,P(第一次正面|结果不同) = p ( 1 − p ) 2 p ( 1 − p ) = 1 2 \frac{p(1-p)}{2p(1-p)} = \frac{1}{2} 2p(1p)p(1p)=21
    • 完全不依赖原始概率 p 的值
  2. 实际注意事项

    • 效率取决于原始硬币的偏向程度
    • 最坏情况 (p≈0 或 p≈1) 需要更多尝试次数
    • 平均所需抛掷次数: 1 2 p ( 1 − p ) \frac{1}{2p(1-p)} 2p(1p)1

此实现可在任何具有二元输出的有偏随机源上使用,不仅限于硬币抛掷场景。


这道面试题的本质是考察候选人将概率论抽象为可计算模型的能力在随机性约束下设计鲁棒算法的思维,这类能力直接对应量化金融中蒙特卡洛模拟、衍生品定价和风险引擎开发的核心挑战。

🔑 核心知识点

  1. 概率论与条件概率

    • 理解独立事件、联合概率分布(如两次抛掷的 P ( H T ) P(HT) P(HT) P ( T H ) P(TH) P(TH)
    • 掌握条件概率消除偏差的核心原理( P ( 正 ∣ H T ∪ T H ) = p q 2 p q = 0.5 P(\text{正}|HT \cup TH) = \frac{pq}{2pq} = 0.5 P(HTTH)=2pqpq=0.5
  2. 随机过程建模

    • 将非理想随机源转化为公平分布(冯·诺依曼提取器)
    • 处理非稳态随机过程(未知概率 p p p 的动态适应)
  3. 算法效率与鲁棒性

    • 计算期望抛掷次数( 1 2 p ( 1 − p ) \frac{1}{2p(1-p)} 2p(1p)1)及最坏情况处理
    • 边界条件防御( p = 0 p=0 p=0/ p = 1 p=1 p=1 的异常熔断)
  4. 金融应用场景

    • 替代伪随机数生成器(如蒙特卡洛利率路径模拟)
    • 构建公平博弈系统(衍生品定价测试用例)

📊 面试评估维度

考察维度具体表现要求本题对应点
数学抽象能力将现实问题转化为概率模型通过两次抛掷的独立事件构造公平条件概率
算法设计能力设计时间/空间高效的随机化算法冯·诺依曼方法的最坏复杂度 O ( 1 / ( p ( 1 − p ) ) ) O(1/(p(1-p))) O(1/(p(1p)))
代码健壮性处理边界输入与极端场景 p = 0 p=0 p=0/ p = 1 p=1 p=1 的异常处理机制
金融直觉关联量化金融中的随机性需求解释公平随机数在期权定价回测中的作用
沟通表达清晰阐释数学证明与工程取舍用条件概率公式证明公平性并讨论效率代价

🧩 典型回答框架

  1. 问题确认

    • 这是一个在有偏随机源下生成公平比特的问题,核心是消除未知概率 p p p 的影响。
  2. 理论方案

    • 提出冯·诺依曼方法:抛掷两次 → 相同则丢弃 → 不同则取第一次结果
    • 数学证明: P ( 输出正 ) = P ( H T ) P ( H T ) + P ( T H ) = p q p q + q p = 0.5 P(\text{输出正}) = \frac{P(HT)}{P(HT)+P(TH)} = \frac{pq}{pq + qp} = 0.5 P(输出正)=P(HT)+P(TH)P(HT)=pq+qppq=0.5
  3. 工程实现

    • 伪代码:

      while True:
          a = coin()  # 第一次抛掷
          b = coin()  # 第二次抛掷
          if a != b:  # 仅接受HT或TH
              return a  # 输出第一次结果
      
    • 关键优化:设置最大重试次数防止 p = 0 / p = 1 p=0/p=1 p=0/p=1 死循环

  4. 金融关联

    • 在衍生品定价中,该方法可为蒙特卡洛模拟提供无偏随机源,避免因硬件RNG偏差导致希腊值计算错误。

💡 核心洞察

  • 深层意图:表面考察概率问题,实则检验候选人在非理想条件下构建可靠系统的能力——这正是量化系统的核心挑战(如处理市场数据的滑点偏差、校正传感器噪声)。
  • 金融隐喻:类似"用噪声市场数据校准无套利模型",需通过数学工具(如测度变换)分离信号与噪声。
  • 高阶能力:区分"理论正确性"与"工程可用性"的思维——尽管算法数学公平,但 p = 0.01 p=0.01 p=0.01 时需约2500次抛掷/比特,需引导候选人讨论效率优化方案(如熵池化缓存)。

风险提示与免责声明
本文内容基于公开信息研究整理,不构成任何形式的投资建议。历史表现不应作为未来收益保证,市场存在不可预见的波动风险。投资者需结合自身财务状况及风险承受能力独立决策,并自行承担交易结果。作者及发布方不对任何依据本文操作导致的损失承担法律责任。市场有风险,投资须谨慎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

船长Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值