目录
3680 字
18 分钟
面试智力题考古:鸡蛋、电梯与井盖的标准答案,以及它们各自的漏洞

一、面试官问的是答案,还是假设?#

智力题在面试里的地位一直很尴尬:候选人觉得它在考脑筋急转弯,面试官说它在考思维方式,双方都不太信对方的说法。更尴尬的是,很多流传甚广的「标准答案」本身是残缺的——它记住了一个结论,却把结论依赖的条件丢了。

下面三道题,分别落在算法、概率和几何里。它们都有一个广为流传的答案,也都至少漏掉了一个关键变量。

题目流行答案漏掉的变量
100 层楼、2 个鸡蛋7 次(二分)鸡蛋数量;碎掉之后的线性成本
电梯平均 30 秒一班平均等 15 秒到达间隔的分布,不只是均值
井盖为什么是圆的圆盖子掉不下去「等宽」不等于「圆」;井沿还有一圈托边

把这三个变量补回去,三道题会从「背答案」变成「建模型」。

二、鸡蛋掉落:7 是无限个鸡蛋的答案#

先复述题面:100 层楼,2 个一模一样的鸡蛋。鸡蛋从某一层落下,若没碎,可以继续用;若碎了就不能再用。要找出「刚好会碎」的临界楼层,问最坏情况下最少要扔多少次。

题面里有几个容易被含糊过去的地方,先钉死:

  • 鸡蛋在 0 层一定不碎,在 100 层一定碎,否则问题无解;
  • 要找的是精确的临界楼层,不是「证明某一层会碎」;
  • 每次扔鸡蛋只有碎和不碎两种结果;
  • 代价按最坏情况计,不按平均情况计。

如果只说「二分」,第一颗鸡蛋从 50 层扔:碎了,剩下 1 颗鸡蛋和 49 个候选楼层,只能从 1 层往上线性扫,最坏再扫 49 次,总共 50 次。没碎当然更好,但最坏情况是 50 次。

7 次是从哪来的?7 是信息论下界:2 的 7 次方是 128,大于 100,每次两种结果,7 次最多区分 128 种情况。这个下界只有在鸡蛋足够多、每次都能二分时才达得到。2 个鸡蛋时,碎掉的那一支付不起继续二分的成本。

正确做法是让第一颗鸡蛋的步长递减。第一次在 14 层扔:

  • 碎了,用第二颗从 1 层线性扫到 13 层,最坏 1 + 13 = 14 次;
  • 没碎,第二次在 14 + 13 = 27 层扔;碎了就从 15 层扫到 26 层,最坏 2 + 12 = 14 次;
  • 还没碎,第三次在 27 + 12 = 39 层扔……

步长是 14、13、12、……、1,总和 105,覆盖 100 层绰绰有余。每一步都在给「碎掉的那一支」留余量:每扔一次没碎,就用掉了一次预算,同时碎掉之后还要线性扫的楼层上限也必须跟着减一。

把这件事写成递推,会得到一个比题面更好用的形式:

// dp[e][t]:e 个鸡蛋、t 次机会,最多能保证测出多少层
function minDrops(floors: number, eggs: number): number {
const maxT = floors; // 1 个鸡蛋时,最坏要扔 floors 次
const dp = Array.from({ length: eggs + 1 }, () =>
new Array(maxT + 1).fill(0),
);
for (let t = 1; t <= maxT; t++) {
for (let e = 1; e <= eggs; e++) {
// 这一次扔下去:
// 碎了 -> 下面还有 e-1 个鸡蛋、t-1 次机会
// 没碎 -> 上面还有 e 个鸡蛋、t-1 次机会
// 再加上当前这一层
dp[e][t] = dp[e - 1][t - 1] + 1 + dp[e][t - 1];
}
if (dp[eggs][t] >= floors) return t;
}
return floors;
}

这个递推和 LeetCode 887 是同一个东西,只是换了个方向:不是问「n 层楼最少扔几次」,而是问「t 次机会最多能覆盖几层」。后者少一层嵌套循环,写起来短很多。

把递推展开,得到闭式:

F(t, e) = C(t, 1) + C(t, 2) + ... + C(t, e)

鸡蛋数为 2 时,它是三角数 t(t+1)/2:t = 13 覆盖 91 层,t = 14 覆盖 105 层,所以 2 个鸡蛋的答案是 14。鸡蛋更多时,100 层对应的最少次数是:

鸡蛋数100 层最少次数
1100
214
39
48
5 及以上7

5 个鸡蛋以上就撞到信息论下界了:7 次两分叉最多区分 128 种情况,100 层已经够用。再多的鸡蛋不会让答案小于 7,只会让「碎了也不心疼」这件事变得更有底气。

面试现场常见的追问是:「如果要求平均次数最少呢?」那是另一个目标函数,答案会变。先问清楚是最坏情况还是期望,再动手。7 和 14 都不算错,错的是不说明鸡蛋数量。

三、电梯:平均等待不是间隔的一半#

电梯题通常有两个版本:一个是「设计调度算法」,一个是「为什么等电梯总觉得久」。前者考系统设计,后者考概率。它们共享一个容易被忽略的前提:电梯到达的间隔不是一个固定值。

先看等待。假设电梯平均 30 秒来一班。直觉说平均等 15 秒,因为「最坏等 30 秒,理想情况 0 秒」。这个直觉只在电梯准点到站时成立:如果每 30 秒准时来一班,你的到达时刻在 30 秒里均匀分布,平均等待确实是 15 秒。

但如果到达间隔是随机的,结论会变。对一个随机到达的乘客,平均等待时间是:

E[W] = E[T²] / (2 · E[T])

其中 T 是相邻两班电梯的间隔。分母是一次间隔的均值,分子是间隔平方的均值。平方会把长间隔放大,所以乘客更容易撞上长间隔——这就是 inspection paradox(检查悖论)。用更直白的话说:你不是在「随机挑一班电梯」,你是在「按时间随机落点」,落点越长的那段间隔,越容易被你踩中。

对泊松过程,间隔服从指数分布,E[T] = 1/λ,E[T²] = 2/λ²,代进去 E[W] = 1/λ,也就是平均间隔本身,而不是一半。30 秒一班,平均等 30 秒;准点 30 秒一班,平均等 15 秒。均值一样,等待差一倍。

到达间隔E[T]E[W]
固定 30 秒30 秒15 秒
泊松,均值 30 秒30 秒30 秒
方差比泊松更大30 秒大于 30 秒

这也解释了为什么「公交车要么不来,要么一起来」:车不是变少了,是间隔的方差变大了;而你在站台等车时,恰好更容易落进那段长间隔。

写几行代码就能看到差别。下面用两种方式生成电梯到达,再让乘客在时间轴上随机出现,量一量等到下一班的时间:

function nextArrivalWait(kind: "fixed" | "poisson", mean = 30) {
const n = 200_000;
const horizon = n * mean * 2;
const arrivals: number[] = [];
let t = 0;
while (t < horizon) {
t += kind === "fixed" ? mean : -mean * Math.log(1 - Math.random());
arrivals.push(t);
}
arrivals.push(t + mean); // 兜底,保证最后一班之后也有下一班
let total = 0;
for (let i = 0; i < n; i++) {
const passenger = Math.random() * horizon;
let lo = 0;
let hi = arrivals.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (arrivals[mid] < passenger) lo = mid + 1;
else hi = mid;
}
total += arrivals[lo] - passenger;
}
return total / n;
}
console.log(nextArrivalWait("fixed")); // ≈ 15
console.log(nextArrivalWait("poisson")); // ≈ 30

这段代码只模拟了「等到下一班」的时间,没有模拟电梯容量、方向和中途停靠。它想说明的也只有一件事:只看平均间隔,算不出平均等待;还得看间隔的分布。

如果题目换成「设计电梯调度」,那就是另一个模型。先把目标函数问出来:是最小化平均等待,还是最小化 p99 等待,还是最大化吞吐、降低能耗?目标不同,算法不同。

策略规则优点失效场景
FCFS按请求顺序服务公平、实现简单满员请求可能拖住整条队列
SSTF先服务最近的请求平均等待短边角楼层可能长期等不到
SCAN沿一个方向扫到尽头再反向吞吐稳定、行为可预测空载时也会跑到楼层尽头
LOOK到最后一个请求就反向比 SCAN 少跑空程请求稀疏时方向切换频繁
目的楼层调度按目的地分组减少停靠次数多一套输入终端和故障路径

经典答案是 SCAN:电梯沿一个方向一路服务,到顶或到底再反向,像磁盘磁头一样。LOOK 是它的实用版:到最后一个请求就反向,不必跑到楼层尽头。它们的共同优点是「可预测」:乘客知道电梯大概会往哪个方向走,不会因为远处来了一个请求就反复掉头。FCFS 公平,但容易被一个满员请求拖住;SSTF 平均等待短,但边角楼层可能一直等不到。

真实楼宇里,调度还要考虑早高峰的上行潮汐、午休的双向流量、消防和疏散模式。目的楼层调度让乘客在进电梯前先输入楼层,系统把去同一层的人分到一组,减少停靠次数;代价是多了一套输入终端和一条新的故障路径。面试里说「用 SCAN」不算错,但只说了算法,没说目标函数和失效场景。

至于关门键,按下去有没有用,取决于电梯的配置;在某些楼里,它更像一个安慰按钮。真正让门关上的,通常是你后退半步,而不是连按三下。

四、井盖:圆不是仅有的解,只是省事的解#

标准答案通常是:圆盖子不会掉进圆洞里,因为圆的宽度在每个方向都一样,都是直径;洞的直径比盖子略小,所以无论怎么转、怎么斜,盖子都卡在洞口。方形盖子就不行:斜着放、立起来,总能找到一个角度掉下去。

这个答案对了一半。对的部分是「等宽」:圆在任意方向上的宽度都相同,而且大于洞的直径,所以无论怎么转、怎么斜,投影都塞不进洞里。错的部分是把「等宽」直接等同于「圆」。等宽曲线不止圆一种。

最常被拿来翻案的是 Reuleaux 三角形:以等边三角形的三个顶点为圆心、边长为半径画三段圆弧,围成的图形宽度处处相等。英国 50 便士硬币的七边形轮廓也是等宽曲线。用 Reuleaux 三角形做井盖,只要它的宽度大于洞的直径,同样掉不下去。所以「井盖为什么是圆的」的严格答案不是「只有圆不会掉」,而是「等宽形状不会掉,而圆是其中更容易制造、更容易滚动、也更不需要对准的一种」。

为什么等宽就够了?把盖子想成一张薄板。无论你怎么倾斜它,它在洞口平面上的投影,在平面内任意方向上的宽度,都等于盖子在该方向上的宽度;等宽形状的每个方向都是同一个宽度 w。一个直径小于 w 的圆洞,装不下任何一个方向宽度都为 w 的图形。方形则不同:它的宽度随方向变化,从边长 s 到对角线 s√2;沿着一条边把盖子倾斜到接近竖直,投影会收成一条长约 s 的细长条。只要 s 小于洞的对角线 a√2(常见的盖子和洞口只差一点余量,这个条件成立),这条投影就能斜着穿过洞口。

形状等宽能掉进匹配的洞现实中的取舍
圆形是不能好滚、不用对准、好加工
正方形否能(斜着或立起来)需要对准,角容易磕
Reuleaux 三角形是不能不好滚,加工麻烦
带托边的任意形状—通常不能托边才是第一道保险

不过,现实中的井盖还有一圈托边:盖子边缘搭在井圈的凸缘上,就算形状允许,它也不会真的掉下去。很多方形、矩形的检修盖板也活得好好的。所以「圆所以掉不下去」只是把几何条件当成了全部原因;施工、制造、搬运、防盗,都是答案的一部分。

面试里更好的回答不是背「圆不会掉」,而是先问:这个「为什么」问的是几何、制造,还是施工?

  • 如果问几何,答案是等宽:圆和 Reuleaux 三角形都不会掉;
  • 如果问制造,答案是圆更容易加工、更容易滚动、更容易存放;
  • 如果问施工,答案是圆洞更容易挖,盖子不用对准。

三个问题,三个答案。最省事的回答是「因为洞是圆的」——这句话当然对,但它把问题原样退回了面试官。面试官如果继续问「那洞为什么是圆的」,你就从几何学滑进了施工队的地盘。

五、三道题的共同点:标准答案都被压缩过#

把三道题放在一起,会发现它们的「标准答案」都遵循同一个压缩方式:保留结论,删掉目标函数和约束。

  • 鸡蛋:删掉了「2 个鸡蛋」和「最坏情况」,于是 7 这个无限鸡蛋的下界被当成了答案;
  • 电梯:删掉了「到达间隔的分布」,于是均值被当成了全部信息;
  • 井盖:删掉了「等宽」和「托边」,于是圆被当成了仅有的解。

面试里真正值钱的动作,是在回答之前把这些被删掉的东西问回来:

  1. 目标是什么?最坏情况、平均情况,还是 p99?
  2. 约束是什么?鸡蛋能不能复用,电梯是不是准点,井盖有没有托边?
  3. 你要的是可行解、下界,还是实现成本低的解?
  4. 你的答案在什么条件下失效?

这四问不需要多聪明,只需要在开口前停两秒。智力题考的往往不是那两秒里的灵感,而是你愿不愿意承认:题目本身没说全。

当然,真实工作里的题面通常更直接:井盖要圆,预算要方,deadline 是昨天,而且没有标准答案——只有下一位面试官。

参考来源#

面试智力题考古:鸡蛋、电梯与井盖的标准答案,以及它们各自的漏洞
https://www.hehonglei.cn/posts/interview-puzzle-archaeology-eggs-elevators-manhole-covers/
作者
Honglei He
发布于
2026-11-07
许可协议
CC BY-NC-SA 4.0