三门问题
题
三扇门,一扇后面是车,两扇后面是山羊。你先选一扇(比如 1 号)。主持人知道车在哪,他从另外两扇里打开一扇山羊门(若两扇都是山羊就随机开一扇),然后问你:要不要换到剩下那扇没开的门?换门赢车的概率是多少?
一句话
换门赢 \(2/3\),坚持只赢 \(1/3\)——主持人无论如何都能开出一扇山羊门,他的动作对"你最初选中车"这件事没有提供任何信息;你那扇门维持 \(1/3\),剩下的 \(2/3\) 全部集中到唯一没开的另一扇门上。
关键技巧
规则里有三个条件,缺一个答案就变:
- 主持人知道车在哪。
- 他必定开一扇你没选的山羊门。
- 他必定给你换门的机会。
在这套规则下,"换门"等价于一个更简单的策略:你最初选错了就赢。最初选错的概率是 \(\frac23\)——这时两扇另外的门里一扇是车一扇是羊,主持人被迫开羊门,剩下那扇必然是车。最初选对(\(\frac13\))换门才输。
用贝叶斯写一遍:设你选 1 号、主持人开了 3 号。
\[ P(\text{车在 2} \mid \text{开 3}) = \frac{P(\text{开 3} \mid \text{车在 2}) \cdot \frac13}{P(\text{开 3})} = \frac{1 \cdot \frac13}{\frac13 \cdot \frac12 + \frac13 \cdot 1 + \frac13 \cdot 0} = \frac23 \]
分子里的 \(1\) 是关键:车在 2 号时主持人别无选择,只能开 3 号;车在 1 号时他两扇都能开,只以 \(\frac12\) 开 3 号。"被迫"的动作比"随意"的动作更有信息量。
解
三种等可能的车位置,你固定选 1 号:
| 车在 | 主持人开 | 坚持 1 号 | 换门 |
|---|---|---|---|
| 1 | 2 或 3 | 赢 | 输 |
| 2 | 3(被迫) | 输 | 赢 |
| 3 | 2(被迫) | 输 | 赢 |
换门赢 2/3。
延伸
- \(n\) 扇门、主持人开一扇:换门时在剩下 \(n-2\) 扇里随机选,胜率 \(\frac{n-1}{n} \cdot \frac{1}{n-2} = \frac{n-1}{n(n-2)}\),永远大于坚持的 \(\frac1n\)。\(n = 10\) 时是 \(\frac{9}{80}\) 对 \(\frac{1}{10}\)。
- \(n\) 扇门、主持人开 \(n-2\) 扇只留一扇:换门胜率 \(\frac{n-1}{n}\)。100 扇门时主持人开掉 98 扇羊门,只留你的和另一扇——这个版本几乎让所有人的直觉转过弯来。
- 主持人不知情(Monty Fall):他随手开了一扇你没选的门,恰好是山羊。此时换与不换都是 \(\frac12\)。区别在于车在 2 号时他也可能开到车——"开出山羊"本身成了一次有信息的观察,把"最初选对"的后验从 \(\frac13\) 抬到 \(\frac12\)。
- 有偏好的主持人:若你选中车时他以概率 \(q\) 开 3 号,那么看到他开 3 号后换门胜率是 \(\frac{1}{1+q}\)。\(q = 1\)(他偏爱 3 号)给 \(\frac12\),\(q = 0\)(他从不主动开 3 号)给 \(1\)——但只要知情且必开羊门,换门永远不亏。
- 地狱主持人(Monty from Hell):他只在你选中车时才给换门机会。一旦被问"要换吗",换门必输。第 3 条规则一改,结论完全反转。
- 常见错误:"剩两扇门所以各 \(\frac12\)"——把"剩下两个选项"当成"两个等可能的选项"。等可能要靠对称性论证,而主持人的知情行为恰恰打破了两扇门的对称。
- 同类坑:两个孩子问题 的 \(\frac13\) 与 \(\frac12\) 之争也是一回事——条件概率的答案由观察事件的生成机制决定,而不是由题面那句话决定。
数值验证
精确枚举各变体(你固定选 0 号门)+ 三门蒙特卡洛:
from fractions import Fraction as F
import random
def monty(n=3, knows=True, q=None, observed=None):
"""你选 0 号门,主持人开 1 扇门且开出来是空门。返回 (坚持, 换门) 胜率,换门 = 在剩余门里均匀选。
knows=False:主持人在 1..n-1 里随手开,开到车的局作废。
q:三门且你选中车时,主持人开 2 号门的概率;observed:只统计开了这扇门的局。"""
stay = switch = seen = F(0)
for car in range(n):
if knows:
cand = [d for d in range(1, n) if d != car]
probs = {d: F(1, len(cand)) for d in cand}
if q is not None and car == 0:
probs = {1: 1 - q, 2: q}
else:
probs = {d: F(1, n - 1) for d in range(1, n) if d != car}
for d, pd in probs.items():
if observed is not None and d != observed:
continue
w = F(1, n) * pd
rest = [x for x in range(1, n) if x != d]
seen += w
stay += w * (car == 0)
switch += w * F(rest.count(car), len(rest))
return stay / seen, switch / seen
show = lambda t: tuple(str(x) for x in t)
print(show(monty())) # ('1/3', '2/3')
print(show(monty(knows=False))) # ('1/2', '1/2') Monty Fall
print(show(monty(n=4)), show(monty(n=10))) # ('1/4', '3/8') ('1/10', '9/80')
for q in (F(0), F(1, 2), F(1)):
print(q, show(monty(q=q, observed=2))) # 换门胜率 1/(1+q):('0', '1') ('1/3', '2/3') ('1/2', '1/2')
random.seed(5)
T, win = 10**6, 0
for _ in range(T):
car, pick = random.randrange(3), random.randrange(3)
host = random.choice([d for d in range(3) if d not in (car, pick)])
win += (3 - pick - host) == car # 换到剩下那扇
print(win / T) # 0.666457
n, T, win = 100, 100000, 0 # 100 门、主持人开 98 扇只留一扇
for _ in range(T):
car, pick = random.randrange(n), random.randrange(n)
keep = car if car != pick else random.choice([d for d in range(n) if d != pick])
win += keep == car
print(win / T) # 0.9901