惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Securelist
C
Cybersecurity and Infrastructure Security Agency CISA
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Security Affairs
Hacker News: Ask HN
Hacker News: Ask HN
L
Lohrmann on Cybersecurity
PCI Perspectives
PCI Perspectives
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
C
Cyber Attacks, Cyber Crime and Cyber Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
月光博客
月光博客
W
WeLiveSecurity
T
Threat Research - Cisco Blogs
Martin Fowler
Martin Fowler
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Recorded Future
Recorded Future
The GitHub Blog
The GitHub Blog
Webroot Blog
Webroot Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
TaoSecurity Blog
TaoSecurity Blog
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
F
Full Disclosure
U
Unit 42
Jina AI
Jina AI
博客园 - 司徒正美
阮一峰的网络日志
阮一峰的网络日志
L
LINUX DO - 最新话题
宝玉的分享
宝玉的分享
大猫的无限游戏
大猫的无限游戏
The Hacker News
The Hacker News
The Last Watchdog
The Last Watchdog
T
Troy Hunt's Blog
腾讯CDC
T
Threatpost
H
Hacker News: Front Page
P
Palo Alto Networks Blog
博客园 - 聂微东
Last Week in AI
Last Week in AI
有赞技术团队
有赞技术团队
Help Net Security
Help Net Security
L
LINUX DO - 热门话题
N
News and Events Feed by Topic
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Spread Privacy
Spread Privacy

Long Luo's Life Notes

夏至日测地球:利用太阳影子计算地球半径 太阳温度是怎么计算出来的? 《大象的时间,老鼠的时间》读书笔记:生命节奏背后的数学规律 小港流到哪里去? 如何用一根棍子测出地球有多大?复刻埃拉托色尼的春分实验 2007江苏高考数学第20题解析:一道通向黄金分割数的数列压轴题 2010年江苏高考数学压轴题解析:巧用余弦定理与数学归纳法 2011年清华大学自主招生数学题解析:一道经典数列题的解法与思路 2011年清华大学自主招生数学题解析:一道经典数列题的解法与思路 2006年江西高考理科数学压轴题解析:递推、放缩与不等式结构 2006年江西高考理科数学压轴题解析:递推、放缩与不等式结构 一道初中数学极值题的多种解法:柯西不等式、几何法、函数法详解 扔几个骰子,怎么算出期望?——拼多多校招笔试算法题的数学故事 拼多多校招笔试算法题:一行公式搞定“多多的魔术盒子” 斯特林公式(Stirling's Formula):我一个阶乘表达式,怎么就和圆扯上关系了呢? 我爱做题:2010年江西高考理科数学压轴题 热机的效率上限在哪里?解析卡诺循环(Carnot Cycle) 为什么 2024 年会有 366 天? 数学之美:几何视角下的高斯积分(Gaussian Integral) 从最小二乘法到正态分布:高斯是如何找到失踪的谷神星的? 正态分布(Normal Distribution)公式为什么长这样? 高速公路编号背后的数学密码 2024阿里巴巴全球数学竞赛预选赛试题及解答 库函数 (libm) 是如何计算三角函数值的? payne hanek 归约算法 音乐背后的数学 素描背后的物理 cody waite 浮点数 Remez Algorithm 参数归约算法(Argument Range Reduction):如何在浮点数环境下计算超大数字的三角函数值? 素描背后的数学 发生在计算机内存里的进化:解密遗传算法(Genetic Algorithm) CORDIC算法:一种高效计算三角函数值的方法 墨卡托的魔术:地图是如何欺骗你的眼睛的? PID 算法到底在干什么?工程师最常用的控制方法 解密卡尔曼滤波(Kalman Filter)算法:深入解析卡尔曼滤波算法原理与在线可视化实例 从记忆到洞察:轻松掌握泰勒展开式(Taylor Series)的记忆技巧 哪个更大呢? $2^{100!}$ 还是 $2^{100}!$ ? Google经典编程竞赛题:计算 $(3 + \sqrt{5})^n$ 的小数点前三位数 手写数字识别:解码机器学习的背后的数学原理 The Answers of MRI Tutorial Videos gdb 操作指南 Linux 网络命令指南 贝塞尔曲线(Bezier Curve):优雅背后的数学原理 LeetCode 380. Insert Delete GetRandom O(1) Data Structures: Thought Process from HashMap to HashMap + Array LeetCode 2475. 数组中不等三元组的数目 2种 O(n) 时间复杂度算法 LeetCode 947. Most Stones Removed with Same Row or Column It is Literally a Graph: DFS and Union Find LeetCode 295. Find Median from Data Stream Two Heaps with the Follow Ups LeetCode 295. Find Median from Data Stream Two Heaps with the Follow Ups LeetCode 1668. 最大重复子字符串 不用API,比KMP更易理解简洁优雅的暴力解法 LeetCode 334. Increasing Triplet Subsequence Why Greedy Works? LeetCode 迷宫问题(The Maze)
Google经典面试题: 鸡蛋应该怎么扔?
Long Luo · 2026-03-06 · via Long Luo's Life Notes

By Long Luo

微软、Google 等科技公司的面试题库中,流传着许多经典问题。小时候在杂志上看过微软那道著名的面试题 ——“为什么下水道井盖是圆的?”,当时觉得这类问题十分有趣,因为它考察的往往不是知识储备,而是分析问题的思维方式。

最近,我又重新翻到了 Google 的一道经典面试题 —— 双蛋问题( \(\textit{Two Eggs Problem}\) )或者说鸡蛋掉落问题( \(\textit{Egg Dropping Puzzle}\) )。与井盖问题不同,这道题更偏向数学推导与算法优化,但同样充满巧思。乍看之下,它只是一个简单的试验设计问题;深入分析后却会发现,其中隐藏着相当优美的最坏情况分析思想,是一道非常值得细细品味的经典题目。

这道题的原文如下:

You work in a \(100\) floor building and you get \(2\) identical eggs. You need to figure out the highest floor an egg can be dropped without breaking. Find an algorithm that is minimizing number of throws in the worst-case scenario.

你站在一栋 \(100\) 层高的大楼里,手中有 \(2\) 个完全相同的鸡蛋。有一未知的临界楼层,鸡蛋从临界楼层以下扔下去,一定不会碎;从临界楼层以上丢下去,一定会碎。已知未碎的鸡蛋可以重复使用,碎了的鸡蛋就不能再往下扔了。要求即便在最坏情况下,尝试次数也要尽可能少。请问最少需要尝试多少次能够找到这个临界楼层?

一个鸡蛋

如果只有 \(1\) 个鸡蛋,问题解法非常简单。我们别无选择,只能从一楼开始逐层测试。用这种方法一定能找到鸡蛋安全掉落的最高楼层:该楼层就是鸡蛋摔碎楼层的楼下一层。

那么在最坏情况下,最少需要尝试多少次?答案就是 \(100\) 次。因为最坏的情况就是临界楼层为第 \(100\) 层。如果鸡蛋在第 \(100\) 层摔碎,则临界楼层为 \(99\) 层。

两个鸡蛋

如果有 \(2\) 个鸡蛋,那么问题就变得有趣起来。此时我们允许试错一次,大家很自然会想到二分查找,但在这里并不是最优方案。如果最高安全楼层是 \(49\) 层,一开始就在 \(50\) 层扔下第一枚鸡蛋,鸡蛋直接摔碎,之后只能逐层测试 \(49\) 次才能得到答案,总计尝试 \(50\) 次。

既然一开始从 50 层扔下不可行,那么很容易想到隔少一点楼层扔下行不行呢?如果我们每隔 \(10\) 层测试一次的话,那么依次在 \(10, 20, 30, 40, 50\) 层扔鸡蛋,鸡蛋在 \(50\) 层摔碎后,再逐层测试 \(41 - 49\) 层即可得出答案,全程仅需 \(14\) 次尝试。由此可见,每隔 \(k\) 层测试一次是可行的思路。在这种策略下在最坏情况下的尝试次数:最坏场景是连续跳层直到鸡蛋摔碎,之后只能用仅剩的一个鸡蛋,在最后一段长度为 \(k - 1\) 的区间内逐层排查。

那么尝试次数计算公式为:\(\lfloor \dfrac {100}{k} \rfloor + (k - 1)\) 。注意到 \(\dfrac {100}{k}\)\(k - 1\) 是此消彼长的关系,在大概 \(k = 10\) 左右有极值,当 \(k = 8, 9, 10, 11, 12, 13\) 时,计算得到最坏尝试次数为 \(19\) 次。

那这个问题的正确答案是扔多少次呢?

递归

如果我们只是想知道鸡蛋最少要扔多少次的话,我们可以通过写程序来得到答案。类似汉诺塔问题,解决此类问题的最佳方法就是递归法,同一楼层我们取最小值就行,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static int eggDropping(int eggs, int floors) {
if (eggs == 1) {
return floors;
}

if (floors == 0 || floors == 1) {
return floors;
}

int minTries = Integer.MAX_VALUE;

for (int i = 1; i <= floors; i++) {
minTries = Math.min(minTries, 1 + Math.max(eggDropping(eggs, floors - i), eggDropping(eggs - 1, i - 1)));
}

return minTries;
}

这个递归法非常耗时,在个人电脑上运行了很长时间也没有执行完。因为递归法的时间复杂度是 \(O(2^n)\) ,显然 \(2^{100}\) 是个天文数字,我们可以缓存下每个楼层的最小值,优化算法,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
static Map<Long, Integer> memoMap = new HashMap<>();

private static long getKey(int eggs, int floor) {
return ((long) eggs << 32) | floor;
}

private static int eggDroppingWithMemo(int eggs, int floors) {
if (eggs == 1) {
return floors;
}

if (floors == 0 || floors == 1) {
return floors;
}

long key = getKey(eggs, floors);
if (memoMap.containsKey(key)) {
return memoMap.get(key);
}

int minTries = Integer.MAX_VALUE;

for (int i = 1; i <= floors; i++) {
int worst = Math.max(eggDroppingWithMemo(eggs, floors - i), eggDroppingWithMemo(eggs - 1, i - 1));
minTries = Math.min(minTries, 1 + worst);
}

memoMap.put(key, minTries);

return minTries;
}

计算得到 \(100\) 层楼 \(2\) 个鸡蛋的情况下,答案是 \(14\) 次。这个 \(14\) 次是怎么扔出来的呢?显然不是刚才每隔 \(k\) 层扔鸡蛋法,那么这个问题的解法到底是怎么样的呢?

到底怎么扔呢?

假设对于 \(n\) 层大楼,在最优解法下,最坏情况也只需要尝试 \(x\) 次就可以覆盖所有楼层,那第一次就应该在第 \(x\) 层扔鸡蛋,这样产生 \(2\) 种情况:

  1. 鸡蛋摔碎,就只能逐层测试 \(1\)\(x - 1\) 层,总尝试次数恰好为 \(x\)
  2. 鸡蛋没碎,下一次就在第 \(x + (x - 1)\) 层测试。如果此时鸡蛋摔碎,再逐层排查中间 \(x + 1, x + 2, \dots, x + (x - 1) - 1\) 楼层,这样总尝试次数依旧是 \(x\)

逆向思维下,我们可以先假设最坏情况的尝试次数固定为 \(x\) ,再反推怎么才能不超过 \(x\) 次测试完所有楼层。

于是我们就得到了最优策略:

设在最优策略下,最坏情况最少尝试次数为 \(x\) 。按照上述规则,第一次测试第 \(x\) 层(覆盖 \(x\) 层楼),第二次测第 \(x + (x - 1)\) 层(再覆盖 \(x - 1\) 层),第三次测第 \(x + (x - 1) + (x - 2)\) 层(再覆盖 \(x - 2\) 层)。依此类推,该策略总共能覆盖的楼层数为等差数列求和:

\[ x + (x - 1) + (x - 2) + \cdots + 2 + 1 = \frac {x(x + 1)}{2} \]

若大楼共 \(n\) 层,只需找到满足 \(\dfrac{x(x + 1)}{2} \geq n\) 的最小整数 \(x\)

显然有:

\[ x^2 + x - 2n = 0 \implies x = \frac{-1 + \sqrt{1 + 8n}}{2} \]

由于 \(x\) 必须为整数,需要向上取整:

\[ x = \left \lceil \frac{-1 + \sqrt{1 + 8n}}{2} \right \rceil. \]

回到我们的问题,\(n = 100\) , 代入上述方程可得: \(\lceil 13.65 \rceil = 14\)

我们也可以轻松写出如下代码:

1
2
3
public static int twoEggDrop(int n) {
return (int) Math.ceil((Math.sqrt(1 + 8 * n) - 1) / 2);
}

多个鸡蛋

上一节我们已经了 \(2\) 个鸡蛋情况下的最优解法,那么 \(3\) 个、 \(4\) 个乃至任意 \(k\) 个鸡蛋应该如何求解呢?

假如有足够多鸡蛋的话,很显然我们可以使用二分法,对于 \(n\) 层大楼,那么至多 \(\left \lceil \log_{2}{n} \right \rceil\) 个鸡蛋和尝试即可。

如果鸡蛋不够多呢?

和刚才推理2个鸡蛋最佳算法一致,可以使用动态规划算法。设大楼共 \(n\) 层,有 \(k\) 枚鸡蛋。假设已经求出了 \(k - 1\) 个鸡蛋对应某个楼层 \(t\) 的解,即已知 \(V(k - 1, t)\) ( \(t \in [1, n]\) ) 。对于 \(k\) 枚鸡蛋的场景,我们任选第 \(k\) 层( \(1 \leq k \leq n\))扔下鸡蛋:

  1. 鸡蛋摔碎:说明最高安全楼层在 \(k\) 层以下,问题简化为 \(k - 1\) 枚鸡蛋、 \(k - 1\) 层楼,对应解为 \(V(e - 1, k - 1)\)
  2. 鸡蛋完好:说明最高安全楼层在 \(k\) 层以上,问题简化为 \(k\) 枚鸡蛋、 \(n - k\) 层楼,对应解为 \(V(e, n - k)\)

可以得到贝尔曼方程( \(\textit{Bellman Equation}\) )递归关系式:

\[ \begin{aligned} V(e, n) = & \min_{k} \left( 1 + \max \{ V(e - 1, k - 1), V(e, n - k) \} \right) \\ &k = 1, 2, \dots, n \end{aligned} \]

根据动态规划递推表达式,我们可以写出如下的动态规划算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static int superEggDrop(int k, int n) {
int[][] dp = new int[k][n + 1];

for (int i = 0; i <= n; i++) {
dp[0][i] = i;
}

for (int i = 1; i < k; i++) {
for (int j = 1; j <= n; j++) {
dp[i][j] = Integer.MAX_VALUE;
}
}

for (int i = 1; i < k; i++) {
for (int j = 1; j <= n; j++) {
for (int m = 1; m <= j; m++) {
dp[i][j] = Math.min(dp[i][j], 1 + Math.max(dp[i - 1][m - 1], dp[i][j - m]));
}
}
}

return dp[k - 1][n];
}

当然这个算法只能计算最坏情况下的最少尝试次数。对于 \(2\) 个鸡蛋 \(100\) 层的情况,第一次在 \(14\) 层测试,鸡蛋摔碎就从 \(1\) 楼开始逐层排查;鸡蛋完好,下一次就在 \(14 + 13 = 27\) 层测试,再之后是 \(27 + 12 = 39\) 层,以此类推。当鸡蛋数量大于 \(2\) 时,需要在动态规划计算过程中额外记录信息,依靠这些记录逆向推导,还原出完整的最优测试步骤。

总结

经典有经典的价值!这道题对思维的考察非常全面,通过假设一个鸡蛋情况只能逐层试探,双蛋情况则考察了递归、动态规划思想,是一道难得的好题!

参考文献

  1. Google经典面试题《100层楼测试两个鸡蛋》剖析
  2. 动态规划 \(O(n^2)\) + 数学推导 \(O(1)\)
  3. 鸡蛋掉落-两枚鸡蛋
  4. 鸡蛋掉落
  5. 双蛋问题
  6. The Two Egg Problem
  7. Bellman equation 贝尔曼方程