“12:00 到 13:00 之间见面先到的人等 15 分钟等不到就走”——这道题几乎是概率论里最出名的口语化题目。标准答案 7/16 被无数教材当成几何概型的入门范例但我见过太多人把 1-(45/60)² 这个式子背下来却说不清 45 是怎么冒出来的一旦把等待时间改成 10 分钟、把见面窗口改成两小时立刻就卡壳。这篇东西打算把这道题从头拆一遍先把“约会”翻译成平面上的一个点再用面积算两人相遇的概率然后用一段蒙特卡洛模拟代码做交叉验证最后把常见变体、反直觉结论和踩坑清单列清楚。读者面很宽——准备面试的、讲课需要素材的、想拿它练手写模拟程序的以及纯粹被朋友问住了想弄明白的都能从里面找到能直接抄走的东西。不需要什么高深前置知识会画坐标系、会写十几行 Python 就够哪怕完全没写过代码前面几节的几何推导也能独立看懂。我尽量把每一步的“为什么”都讲透因为这类题真正的价值不在于那个分数而在于它示范了一整套处理随机问题的思维方式。1. 题面拆解三个没说出口的假设1.1 光靠“12:00 到 13:00 之间”这句话算不出概率题目只说了“两人约在 12:00-13:00 之间到”这句话本身信息量是不够的。想一下极端情况就明白了如果这两个人都习惯踩点几乎总是 12:00 到那相遇概率接近 1如果一个人永远 12:00 到、另一个永远 12:50 到那概率接近 0。同样是“12:00 到 13:00 之间到”答案能差出一整个区间。所以“概率”这个词在这里不是免费的它必须挂靠在某个到达时间的分布上。这也是我给别人讲这道题时最先强调的一点不是题目有歧义而是题目默认了一套大家心照不宣的约定。约定不写出来答案就无从谈起约定一变答案跟着变。后面第 5 节我会专门拿几个“改了分布”的变体来演示这种敏感性。1.2 为什么“均匀分布 连续时间”是最省事的一套建模行业里处理这类问题的标准做法是补上两条假设两人各自独立地在 12:00 到 13:00 这个区间内均匀分布地随机到达时间是连续的可以精确到任意小数位。这两条一加问题立刻变得规整——均匀分布意味着样本空间是一块面积均匀的平面区域概率就等于面积占比连积分都省了。选“连续”而不是“按分钟离散”也有讲究。连续时间下任何一个点的概率都是零所以“等待不超过 15 分钟”到底是 ≤ 还是 对结果毫无影响边界可以随便画。这个性质在推导时能省掉大量纠结。作为对比如果老老实实按整分钟枚举答案会漂到 0.4437跟 0.4375 差出 0.6 个百分点——这个现象我在第 4 节会拿代码复现它也是很多人“验算对不上”的元凶。1.3 把生活语言换成符号一行都不能省设甲到达的时刻为 x乙到达的时刻为 y单位统一用“距 12:00 的分钟数”于是 0 ≤ x ≤ 60、0 ≤ y ≤ 60。两人相遇的条件是谁先到谁就等对方等待时长不能超过 15 分钟。翻译成不等式就是 |x − y| ≤ 15——两人到达时间之差的绝对值不超过 15 分钟。这里有三个隐藏前提值得单独点出来。第一“先到的人等 15 分钟”意味着后到的人不需要等他到场就算碰上所以只有一个方向的等待约束生效但方向不确定因此用了绝对值。第二两人之间没有任何额外通信手段不能打电话说“我堵车了”否则概率直接变成 1模型就废了。第三等待策略是刚性的不会因为“感觉对方快来了”而多等五分钟。这三条在现实里都未必成立但它们是这道题能有一个干净答案的前提。提示写题解时把这几个假设明明白白列在开头比直接甩公式专业得多。很多争论其实是假设不同不是数学错了。2. 画个正方形7/16 是怎么来的2.1 样本空间是一块 60×60 的正方形把 x 当横轴、y 当纵轴所有可能的到达组合就构成了平面上一块边长 60 的正方形面积 3600。因为两人独立且均匀这个正方形里每一个点被“抽中”的可能性是一样的于是任何事件的概率就等于它对应区域的面积除以 3600。这一步是整个解法的地基。我特别想提醒一句这个 3600 是面积不是“3600 种可能”。初学者最容易在这里把连续问题偷偷离散化脑子里想成“每人有 60 种到达时刻一共 60×60 种组合”然后开始数格子。数格子不是不行但会引入系统性偏差后面会看到偏差有多大。2.2 45 这个数字必须掰开揉碎讲清楚相遇区是两条平行斜线 y x 15 和 y x − 15 夹出来的那条带子。反过来说“错过”发生在带子外面的两个角落。以右上角为例x − y 15意思是甲比乙晚到超过 15 分钟乙早就走了。这个区域是什么形状在正方形里它由 y x − 15 定义也就是 y 从 0 一直取到 x − 15而 x 最大只能到 60。关键就在这儿当 x 15 时y 的上界是 0区域刚刚开始出现当 x 60 时y 的上界是 45。所以这个三角形两条直角边分别是 60 − 15 45。很多人卡住就是因为直接拿 15 去算或者拿 60 去算唯独想不到是 60 减 15。你可以这样理解x 要“浪费”掉前面 15 分钟才可能开始错过剩下能用的跨度就只有 45 分钟。左下角对称另一个三角形也是边长 45。2.3 面积一除答案落地两个等腰直角三角形每个面积 45×45÷2 1012.5合计 2025。于是错过的概率是 2025/3600 0.5625 9/16相遇的概率是 1 − 9/16 7/16 0.4375也就是 43.75%。换成人话这套约会安排其实相当不靠谱超过一半的概率56.25%两个人根本碰不上面。我每次讲到这里都会停一下让这个数字沉一沉——直觉上很多人觉得“等 15 分钟挺久了”实际效果远没有想象中好。2.4 拿极端值给公式做体检算完别急着交卷代入两个极端情形验一下。等待时间取 0 分钟P 1 − (60/60)² 0两个人必须分秒不差地同时到概率为零符合预期。等待时间取满 60 分钟P 1 − (0/60)² 1只要来了就一定能碰上也对。再看一个中间值等待 30 分钟P 1 − (30/60)² 0.75。等待时间翻了一倍概率从 0.4375 涨到 0.75涨幅约 71%不是翻倍——这个非线性关系在第 6 节还会展开。养成用端点值验算的习惯能挡掉绝大多数低级错误这个动作花不了三十秒收益极高。3. 一般化等待 t 分钟、窗口 T 分钟、两人还不一样3.1 对称情形的通式只要一行把 15 换成 t、60 换成 T错过区的三角形直角边就是 T − t面积 2 × (T − t)²/2 (T − t)²。于是P 1 − (T − t)² / T² 2(t/T) − (t/T)²这个式子有个特别漂亮的性质概率只取决于 t/T 的比值跟绝对时长无关。等待 15 分钟、窗口 60 分钟比值 0.25和等待 30 分钟、窗口 120 分钟比值也是 0.25答案完全一样都是 0.4375。这个洞察非常实用——有人把题面里的“一小时”改成“两小时”想为难你你只要看一眼等待时间是不是也跟着翻了倍就知道答案变没变。3.2 两人等待时长不同先到者等 a、后到者等 b现实里更常见的是不对称比如一方愿意等 20 分钟另一方只肯等 10 分钟。这时候条件变成 −a ≤ x − y ≤ bx y 时 x 先到、等 a 分钟x y 时 y 先到、等 b 分钟。错过区还是两个三角形直角边分别是 T − a 和 T − b于是P 1 − [(T − a)² (T − b)²] / (2T²)拿 a 20、b 10、T 60 试一下1 − (1600 2500)/7200 1 − 0.5694 ≈ 0.4306。作为对照两人都等 15 分钟是 0.4375两人各让一步反而不如一起等 15 分钟划算。这个结论挺有意思在总等待预算固定的情况下把等待时间平均分配效果最好因为平方项对大的那一头惩罚更重。再验一个退化情形a 15、b 0也就是只有先到的人等 15 分钟后到的人一到就走。公式给出 1 − (2025 3600)/7200 0.21875正好是 7/16 的一半左右。这个结果符合直觉——只有一半的场景先到者确实是等的那一方在起作用。3.3 一张参数表抄下来就能用把常用组合算好放在这儿遇到类似的题直接查窗口 T分钟等待 t分钟t/T相遇概率6050.0830.159760100.1670.305660150.2500.437560200.3330.555660300.5000.750060450.7500.9375120150.1250.2344120300.2500.437530150.5000.7500注意最后两行和第四行120/30 和 60/15 都是 0.25 的比值答案一致30/15 和 60/30 都是 0.5也都落在 0.75。这张表本身就是“比值决定一切”的最好证据。4. 代码验证蒙特卡洛、离散化陷阱与收敛速度4.1 模拟的骨架只有三步思路朴素到不能再朴素重复撒点、判断、统计。每次试验里独立生成两个 [0, 60] 上的均匀随机数当作到达时刻判断差的绝对值是否不超过 15是就记一次“相遇”最后用相遇次数除以总次数。大数定律保证这个比值会向真实概率收敛样本越多贴得越近。这套方法的价值不在于求这道题——解析解早就有了——而在于它是检验解析解的独立通道。人算面积的时候容易漏乘 2、容易把 45 写成 30程序不会犯这类错。我在实际工作中养成了一个习惯凡是能用模拟验证的推导都用几十行代码验一遍两边的数字对上了才敢往文档里写。4.2 完整代码直接跑import numpy as np def sim_meet_prob(n, wait15.0, window60.0, seed42): rng np.random.default_rng(seed) a rng.uniform(0.0, window, n) b rng.uniform(0.0, window, n) hit np.abs(a - b) wait p hit.mean() se np.sqrt(p * (1.0 - p) / n) # 标准误 return p, se for n in (1_000, 10_000, 100_000, 1_000_000, 10_000_000): p, se sim_meet_prob(n) lo, hi p - 1.96 * se, p 1.96 * se print(fn{n:10,} p{p:.5f} se{se:.5f} 95%CI[{lo:.5f}, {hi:.5f}])跑出来的典型结果大致是n 1000 时 p 落在 0.43 附近但抖动明显n 100000 时能稳定到 0.437 上下n 10000000 时基本能压到 0.4375 的第三位小数。理论值 0.4375 每次都应该落在 95% 置信区间里如果连续几次都落在外面那就说明代码有 bug而不是运气问题。注意固定随机种子有好处也有坏处。好处是结果可复现写文档、给别人演示都方便坏处是容易掩盖“种子恰好偏了好运”的错觉。我的做法是至少换三个不同的种子各跑一遍看结果是不是都在区间内。4.3 想要几位小数精度就得开多大的样本量标准误的公式是 se √(p(1−p)/n)。代入 p ≈ 0.4375得到 p(1−p) ≈ 0.2461。想要 se ≤ 0.001即小数点后三位大致可信需要 n ≥ 0.2461 / 10⁻⁶ ≈ 24.6 万次。想要 se ≤ 0.0001样本量得翻一百倍到 2460 万次。这个估算非常值得记住它是判断“别人报的模拟结果靠不靠谱”的快速标尺。有人拿一万次模拟跑出 0.4361 就宣称验证了理论值严格讲只能说“不相矛盾”精度根本不够。反过来说如果解析解是 0.4375 而模拟给了 0.44你不用慌多半只是样本量不够如果模拟给了 0.5那就一定是模型理解错了。4.4 离散枚举出来的 0.4437 是什么鬼有人喜欢用整分钟枚举把时刻取成 0, 1, 2, …, 60 共 61 个整数点均匀独立地挑两个判断差值是否不超过 15。from itertools import product hit total 0 for x, y in product(range(61), repeat2): total 1 if abs(x - y) 15: hit 1 print(hit, total, hit / total) # 1651 3721 0.443697...结果是 1651/3721 ≈ 0.4437比 0.4375 高出了 0.6 个百分点。差值从哪来离散化把“刚好差 15 分钟”这一整层格点都算进了相遇区而在连续模型里这条边界线的面积是零完全没有权重。这就是离散化偏差的典型样貌——它不随机是系统性的样本量再大也消不掉只会越来越稳地收敛到那个偏高的值。顺带说一句如果改用 0 到 59 共 60 个整点答案会变成 0.45偏差更大。所以“按分钟枚举”这件事连格子怎么切都得说清楚否则结果根本不可比。5. 变体与反直觉结论5.1 六个变体对照表原题改一个条件答案就可能面目全非。我整理了六种最常被问到的改法变体描述关键改动相遇概率原题T60双方各等 150.4375等待时间改成 30 分钟t300.7500见面窗口改成两小时T120t150.2344一方等 20 分钟、一方等 10 分钟不对称0.4306只有先到者等 15 分钟后到者不等a15b00.2188其中一人雷打不动 12:00 到退化成一个均匀变量0.2500最后一行值得单独验证一下甲固定在 12:00 到乙均匀分布在 0 到 60 之间两人相遇当且仅当乙在 0 到 15 分钟内到概率 15/60 0.25。这个数字比原题的 0.4375 低不少说明“一个人守时”并不能带来什么优势反而压缩了随机性带来的容错空间。5.2 等待时间翻倍概率并没有翻倍把 t 从 15 加到 30概率从 0.4375 涨到 0.75涨幅 71%从 30 加到 45从 0.75 涨到 0.9375涨幅只有 25%。这就是 P 2(t/T) − (t/T)² 里那个负的平方项在起作用t 小的时候近似线性增长斜率 2/Tt 一大就开始“减速”。用这个凹函数的形状还能解释一个反直觉现象等待时间从 0 加到 15 分钟收益是 0.4375从 45 加到 60收益只有 0.0625。也就是说前 15 分钟的等待最值钱。现实里约定的“等一刻钟就走”其实是个相当精明的选择再往上加时间的边际收益掉得很快。另一个反直觉点是窗口扩大的代价。同样是等 15 分钟窗口从 1 小时拉到 2 小时概率从 0.4375 掉到 0.2344几乎腰斩。约一个“两小时之内”的模糊时间比约一个“一小时内”的模糊时间要危险得多。这条结论放到任何需要双方随机到场的场景里都成立比如临时碰头、上门取件、面试候场。6. 高频坑与排查清单6.1 六个最容易踩的坑第一个坑把 45 和 15 搞混。45 是错过区三角形的直角边60 − 1515 是等待时间。有人在面积公式里写 15²得出 225/3600 0.0625 的错过率方向就反了答案会变成 0.9375。第二个坑算面积只算一个三角形。错过区是上下两块对称的漏乘 2 会让答案变成 1 − 1012.5/3600 ≈ 0.719错得离谱。第三个坑用线性估算。有人直接算 15/60 0.25 或者 30/60 0.5。这类估算连量级都不对正确值 0.4375 几乎是 0.25 的两倍。第四个坑把离散和连续的边界处理混着用。连续模型下 ≤ 和 无所谓离散枚举下就必须明确会直接改变结果0.4437 vs 更低的某个值。第五个坑单位不统一。等待时间用“15 分钟”窗口用“1 小时”算面积时没换单位结果变成 15²/1² 之类的东西。第六个坑误读等待规则。有人理解成“两个人都要等对方 15 分钟才算碰到”这等价于要求 |x−y| ≤ 15 且双方都还没走实际约束一样但如果理解成“两人都得原地待满 15 分钟”那就完全跑偏了。规则读清楚比算得快更重要。6.2 我自己的自查三步法第一步端点检验。把 t 0 和 t T 代进去结果必须是 0 和 1。任何不满足这一条的公式直接推倒重来。第二步模拟对照。跑一遍第 4 节的代码看解析解是否落在 95% 置信区间内。这一步大概花两分钟但能挡掉九成以上的计算错误。第三步量纲检查。最终答案必须是纯数字不能带“分钟”这种单位。如果算出来是个带量纲的东西说明面积比值那一步出问题了。7. 迁移场景与动手练习7.1 这套“面积法”在工程里的影子同一套几何思路在很多地方都能看到影子。比如两个服务进程在同一个时间窗内各自随机发起重试如果重试间隔重叠就会撞到一起再比如客服系统的超时设置——用户和坐席各自随机在某个时间窗口内就绪窗口重叠才有对话机会。这些场景的共同结构是两个独立的均匀随机量加一个容差阈值问“两者之差在阈值内”的概率。一旦识别出这个结构问题就从“具体业务”抽象成了“正方形加一条带子”直接套 P 2k − k²k 为容差与窗口之比就能估算。我做过几次粗略的容量规划估算用这个式子估出来的重叠率跟实测数据差得不多作为快速判断已经够用。当然真实系统里分布往往不是均匀的比如用户请求会集中在整点前后这时候得换分布重新算方法是把正方形换成密度曲面思路不变。7.2 三个可以自己动手改的练习题练习一把窗口改成 45 分钟、等待改成 12 分钟先手算再用代码验证。这题的目的是练比值思维答案应该和原题一致。练习二如果两人不是均匀到达而是都在 12:30 附近正态分布标准差 10 分钟相遇概率会怎么变提示分布越集中两人的到达时间越接近概率会明显上升。这个用模拟比推导快得多。练习三三个人版。三个人各自独立均匀到达先到的人等 15 分钟其他人到场也算“碰上了”问至少两人相遇的概率。这题的解析解要分好几种情况讨论用模拟跑一下会轻松很多也能顺便体会到解析方法的边界在哪。最后分享一个我常用的讲法。给别人解释这道题时我从来不上来就写公式而是先画那个正方形用手指沿着对角线比划一条带子说“这就是能碰上的所有可能”然后指着角落说“这两块是彻底错过的”。图一旦立住45 这个数字就不需要背了——它是 60 减 15 减出来的你甚至能当场推。公式会忘图不会。