近日刷题刷到了这个问题,LeetCode 887. Super Egg Drop。/584. Drop Eggs II
首先读题就读了很久,还是不能很好的明白题目具体的意思,一搜索才知道这是一道谷歌的经典的面试问题。这也反应出自己的刷题量还是远远不够啊。本文主要是参考了以下两篇文章写成,特此标注。
The Two Egg Problem
【直观算法】Egg Puzzle 鸡蛋难题
文章目录
1. 题目描述
有栋楼高100
层,一个鸡蛋从第x
层以上(>x
)扔下来会碎,但是从第x层和以下的(<=
)的楼层扔下来则不会碎。给定2
个鸡蛋,设计一种策略找出x
,保证在最坏的情况下,最小扔鸡蛋尝试的次数
1.1 求什么?
找到第x
层扔下不会碎,x+1
层扔下会碎的临界层所需要的最少的尝试次数r
- 也就是说,我们最后要返回的是保证能找到哦临界层所需要的,最小的尝试次数。
2. 一个鸡蛋的情况
如果我们手上只有一个鸡蛋,同样必须要找到临界层x
,一个很容易想到的方法是:从低到高的一层一层的尝试。因为必须保证要找到这样的临界层,所以只能从低到高的尝试
- 首先在第
1
层扔,如果没碎,那么继续在第2
层扔。 - …
- 这样一层一层的尝试,如果最后在第
x(x < 100)
层碎了,那么我们的尝试次数就是x
那么一个鸡蛋的情况下,答案是不是就是x
呢?
显然不是,因为我们根本无法判断会在哪一层就碎掉,题目也没有提供这样一个函数接口来判断。这个时候一定要注意题目的描述在最坏的情况下,最小的扔鸡蛋尝试的次数。
思考什么是最坏的情况?假设,我当前的楼层的高度为100
,虽然我无法知道在哪一层鸡蛋会碎,但是我知道这个值是一定在1 <= x <= 100
的,那么最坏的情况就是在第100
层碎掉嘛,那么在这个最坏的情况下,最少的尝试次数肯定也是100
次,因为必须要保证找到这个x
,我尝试100
次,肯定是可以保证找到这个x
的。
所以,综上,一个鸡蛋的情况,在最坏的情况下,最少的尝试次数就是楼层的高度
3. 无数个鸡蛋的情况
假设现在有无数个鸡蛋可以使用,或者说有尽可能多的鸡蛋可以使用,这种情况又该怎么分析呢?
因为我现在有无数个鸡蛋可以使用,那我根本就不关系这个鸡蛋碎还是不碎,因为碎了拿一个新的就可以了。那么这个时候,很容易想到二分法的思路。
-
首先拿一个鸡蛋在第
50
层扔下来,看是否碎掉- 如果碎了,说明临界层答案一定在
[1,50]
- 如果没碎,说明临界层答案一定在
[51,100]
- 如果碎了,说明临界层答案一定在
-
那么不管是否碎掉,临界层答案的空间都缩减了一半。下一次尝试再在子区间的中点进行尝试即可。
-
那么这种方案最坏的情况就是在第
1
层碎掉或者在第100
层碎掉 -
那么需要尝试的次数为 l o g 2 n log_2n log2n, n n n是楼层高度
-
l o n g 2 100 = 6.644 long_2100 = 6.644 long2100=6.644,那么这里我们应该取
6
还是7
呢?- 因为我们是要保证在最坏的情况下都要找到这个临界层,所以要向上取整,就是
7
次,这里可以简单模拟一下,假设在第100
层碎掉,我们按照上面的策略,依此的搜索子区间为: - 假设在第中间层不碎
- [ 1 , 100 ] , [ 51 , 100 ] , [ 76 , 100 ] , [ 89 , 100 ] , [ 95 , 100 ] , [ 98 , 100 ] , [ 100 , 100 ] [1,100],[51,100],[76,100],[89,100],[95,100],[98,100],[100,100] [1,100],[51,100],[76,100],[89,100],[95,100],[98,100],[100,100]
- 那么只需要
7
次尝试就能在最坏的情况下完成
- 因为我们是要保证在最坏的情况下都要找到这个临界层,所以要向上取整,就是
-
综上无数个鸡蛋的情况,最坏的情况下最少的尝试次数是 ┌ l o g 2 n ┐ \ulcorner log_2^n \urcorner ┌log2n┐(向上取整), n n n是楼层高度
4. 两个鸡蛋的情况
在理解了一个鸡蛋和无数个鸡蛋的情况的基础上,对于两个鸡蛋我们应该有初步的思路了。我们可以先拿一个鸡蛋出来试错,大致估计出临界层答案所在的区间,然后再利用剩下的一个鸡蛋从低到高一层一层的尝试,这其实就已经退化成了上文中一个鸡蛋的解法。
下面用具体的例子再阐述一下,更方便理解,现在使用鸡蛋1来确定答案可能所在的区间:
-
先在第
50
层把鸡蛋1扔下去,那么一定会存在两种结果:- 不碎,那么临界层答案一定在
[51,100]
,因为没有碎,我们可以继续使用鸡蛋1去缩小答案可能所在的区间,例如接下来在第75
层继续扔 - 碎掉,那么临界层答案一定在
[1,50]
层,因为现在鸡蛋1碎了,只剩下鸡蛋2可以使用了,那么为了保证能够找到临界层,就需要从第1
层到第49
层逐层尝试,那么最坏的情况就是到第49
层都不碎,那么就选需要尝试49
次,加上第50
层的尝试,一共就是50
次尝试。
- 不碎,那么临界层答案一定在
-
假设在第
75
层把鸡蛋扔下去,也肯定存在两种结果:- 不碎,那么临界层答案一定在[76,100]
- 碎了,因为鸡蛋1在第
50
层是不碎的,那么临界层答案一定在[51,75]
,需要从51->74
层逐层尝试,假设最坏的情况是到第74
层都不碎,那么一共尝试了74-51+1 = 24
,再加上第50,75
层的尝试,一共尝试了26
次。
这里我们需要注意到一个非常重要的点,不管是从多少层扔下去,结果只有两个,碎或者是没碎,这种情况背后就一定是树形结构
数据结构和实际问题想联系的能力,需要加强训练
- 没有分叉,一路推理:线性结构
- 决策结构有分叉:树型结构
- 推理过程中,产生交汇:图结构
所以我们要把上面的尝试转换为树型结构。
4.1 树型结构
上面的分析,更抽象一点的表达如下,对于鸡蛋1,我们需要选择一个策略,分别在第 K 1 K_1 K1层、 K 2 K_2 K2层、 K 3 K_3 K3层 、 ⋯ \cdots ⋯、 K p K_p Kp层尝试扔鸡蛋,那么在每一层扔,都会有两种结果
-
如果没碎,则继续在第 K i + 1 K_{i+1} Ki+1层继续扔
-
如果碎了,则在区间 [ K i − 1 , K i − 1 ] [K_{i-1},K_{i} - 1] [Ki−1,Ki−1]上用鸡蛋2进行逐层的尝试,(假设 K 0 = 0 K_0=0 K0=0)
-
如果表达为二叉树,就是如图所示。(图片来自文章【直观算法】Egg Puzzle 鸡蛋难题
)
-
假设在第 K p K_p Kp层,鸡蛋碎了,那么总的尝试次数就是: K p + K p − K p − 1 − 1 K_p + K_p - K_{p-1} - 1 Kp+Kp−Kp−1−1
-
这里其实很好理解: K p K_p Kp是鸡蛋1的尝试次数, K p − K p − 1 − 1 K_p - K_{p-1} - 1 Kp−Kp−1−1是鸡蛋2的尝试次数
-
从树的角度来理解:这颗树的节点总数一定就是楼层的高度
N
-
K p − K p − 1 − 1 K_p - K_{p-1} - 1 Kp−Kp−1−1是鸡蛋2的尝试次数,也是节点 K p K_p Kp的子树的高度
-
求解的目标是总的尝试次数最小,其实就是让树的高度最小
-
那么到这里,问题就变成了,一棵树,在节点总数固定的情况下,树的高度要最小
-
显然,我们可以想到满二叉树是满足这样的性质的。
-
那么接下来的问题就是,如何选择鸡蛋1的策略,也就是如何选择 K 1 , K 2 , ⋯ , K p K_1,K_2, \cdots,K_p K1,K2,⋯,Kp,让树的高度最小
4.2 树的高度最小
上面的叙述中,我们重新定义了问题,是在给定的树节点 K 1 , K 2 , ⋯ , K p K_1,K_2, \cdots,K_p K