问题速览
三扇门,1 车 2 羊。你选 A,知情 主持人打开一扇羊门 B,问换不换?直觉 50%,实际 换门胜率 2/3。
直接上代码
typescript
// 三门问题模拟器
// 运行:npx ts-node montyHall.ts
interface GameResult {
myChoice: number; // 我的初始选择
hostOpens: number; // 主持人打开的门
switchTo: number; // 换门后的选择
initialWin: boolean; // 不换是否赢
switchWin: boolean; // 换门是否赢
}
function montyHallSimulation(): GameResult {
const car = 0; // 车在 0 号门(固定布局,通过随机选择模拟不确定性)
// 我随机选一扇门(0, 1, 2)
const myChoice = Math.floor(Math.random() * 3);
// 主持人知道答案,必须开一扇羊门,且不能是我选的
// 找出所有可选的羊门:不是车,且不是我选的
const availableGoats: number[] = [];
for (let i = 0; i < 3; i++) {
if (i !== car && i !== myChoice) {
availableGoats.push(i);
}
}
// 如果我选的是车,availableGoats 有 2 个(1 和 2),主持人随机开一个
// 如果我选的是羊,availableGoats 只有 1 个(另一个羊),主持人被迫开它
const hostOpens = availableGoats[Math.floor(Math.random() * availableGoats.length)];
// 换门:选既不是我的也不是主持人开的
const switchTo = [0, 1, 2].find(i => i !== myChoice && i !== hostOpens)!;
return {
myChoice,
hostOpens,
switchTo,
initialWin: myChoice === car,
switchWin: switchTo === car
};
}
// 跑 10 万次
const ITERATIONS = 100000;
let switchWins = 0;
let stayWins = 0;
for (let i = 0; i < ITERATIONS; i++) {
const result = montyHallSimulation();
if (result.switchWin) switchWins++;
if (result.initialWin) stayWins++;
}
console.log(`迭代次数: ${ITERATIONS}`);
console.log(`换门胜率: ${(switchWins / ITERATIONS).toFixed(4)} (${switchWins})`);
console.log(`不换胜率: ${(stayWins / ITERATIONS).toFixed(4)} (${stayWins})`);
console.log(`胜率比: 换门 / 不换 = ${(switchWins / stayWins).toFixed(2)}`);
// 额外验证:初始选择分布
const choiceCount = [0, 0, 0];
for (let i = 0; i < ITERATIONS; i++) {
const result = montyHallSimulation();
choiceCount[result.myChoice]++;
}
console.log(`\n初始选择分布: [${choiceCount.join(', ')}]`);
运行输出
makefile
迭代次数: 100000
换门胜率: 0.6671 (66710)
不换胜率: 0.3329 (33290)
胜率比: 换门 / 不换 = 2.00
初始选择分布: [33442, 33289, 33269]
代码输出解读:为什么是 0.666?
ini
初始选择概率分布:
├─ 选 0 号门(车):概率 1/3 → 换门 = 输
├─ 选 1 号门(羊):概率 1/3 → 主持人被迫开 2 号 → 换到 0 号 = 赢
└─ 选 2 号门(羊):概率 1/3 → 主持人被迫开 1 号 → 换到 0 号 = 赢
P(换门赢) = 1/3 × 0 + 1/3 × 1 + 1/3 × 1 = 2/3 ≈ 0.6667
关键 :你初始选错的概率是 2/3 ,而选错时主持人没有选择权------他被迫指向唯一的车。这个"被迫"就是信息。
关键逻辑拆解:主持人"被迫"开门的条件分支
typescript
// 核心逻辑可视化
// 情况 1:我选对了(概率 1/3)
if (myChoice === car) {
// 主持人有选择权:B 和 C 都是羊,随机开一个
// availableGoats = [1, 2]
// hostOpens = random.choice([1, 2])
// 换门结果 = 另一个羊 → 输
}
// 情况 2:我选错了(概率 2/3)
else {
// 主持人没有选择权:只有一个羊可以开
// availableGoats = [唯一的另一个羊]
// hostOpens = 被迫指向那个羊
// 换门结果 = 唯一剩下的车 → 赢
}
表面行为一致:无论对错,主持人都开羊门、都问换不换。
背后约束不同:
- 选对时 → 随机排除(无信息)
- 选错时 → 被迫指向答案(高信息)
你的 2/3 初始错误概率 × 选错时换门必赢 = 2/3 总胜率。
技术映射:文档 vs 源码(信息隐藏)
这和读第三方 SDK 源码时遇到的陷阱一模一样:
typescript
// 表面 API:看起来一致
interface HostAPI {
openDoor(myChoice: number): number; // 总是返回一个羊门
}
// 实际实现:约束条件完全不同
class InformedHost implements HostAPI {
openDoor(myChoice: number): number {
if (this.isCorrect(myChoice)) {
// 我选对了:主持人在两个羊中随机
return this.randomGoat();
} else {
// 我选错了:主持人被迫指向唯一的车(通过排除另一个羊)
return this.forcedGoat(myChoice);
}
}
}
文档只暴露接口签名 ,但实现约束决定了信息结构。
读源码就是在读"知情排除"的规则------和三门问题完全同构。
变体代码对比:知情 vs 不知情主持人
版本 A:知情主持人(原题)→ 换门 2/3
typescript
function informedHost(car: number, myChoice: number): number {
// 知道答案,精准避开车
const goats = [0, 1, 2].filter(i => i !== car && i !== myChoice);
return goats[Math.floor(Math.random() * goats.length)];
}
版本 B:不知情主持人 → 换门 1/2
typescript
function randomHost(car: number, myChoice: number): number | null {
// 不知道答案,随机开一扇不是我选的
const others = [0, 1, 2].filter(i => i !== myChoice);
const opened = others[Math.floor(Math.random() * others.length)];
// 如果开到车,游戏结束(这种情况要排除)
if (opened === car) return null;
return opened;
}
关键差异
| 维度 | 知情主持人 | 不知情主持人 |
|---|---|---|
availableGoats 长度 |
1 或 2(取决于我是否选对) | 固定 2 |
| 是否可能开到车 | 不可能 | 可能(1/3 概率) |
| 换门胜率 | 2/3 | 1/2 |
| 直觉是否正确 | 错误 | 正确 |
区别就在"故意"二字------知情排除携带信息,随机排除不携带。
一句话总结
主持人"总是做同样的事"是策略设计,不是随机噪声。他的一致性提问掩盖了不对称约束:你选错时他被迫替你指向答案。换门就是在系统性收割你 2/3 的初始错误概率。
扩展:100 扇门版本
typescript
function montyHall100Doors(): boolean {
const totalDoors = 100;
const car = 0;
const myChoice = Math.floor(Math.random() * totalDoors);
// 主持人从剩下的 99 扇中,精准避开唯一的车,打开 98 只羊
const availableGoats = [0, 1, 2].filter(i => i !== car && i !== myChoice);
// ... 压缩后只剩我的选择和另一扇门
// 换门胜率 = 99/100
return true;
}
极端情况让直觉修正更容易:你初始选错的概率 99/100,主持人帮你把 99 扇门压缩成 1 扇,那扇门几乎必然是车。
最近在重建概率认知体系,用代码验证每一个反直觉结论。