一幢 100 层的大楼,给你两枚鸡蛋。假设,在第 n 层扔下鸡蛋,鸡蛋不碎,那么从第 n-1 层扔鸡蛋,鸡蛋也不会碎。两个鸡蛋一模一样,不碎的话可以扔无数次。目标是利用这两个鸡蛋找出临界楼层 t , 使得鸡蛋从 t 层扔下不会碎, 从 t+1 层扔下会碎。
现要求回答, 最少需要多少次尝试, 才能保证在最坏的情况下,找到楼层 t , 且需要给出尝试的策略。
- 明确问题
-
题目要求是考虑 在策略可能遭遇的 最坏情况下,尝试次数最少。 这个地方有些绕,但是必须搞清楚。
-
举个例子, 如果你用二分法,第一次把鸡蛋从50层扔下去, 最坏的情况是临界层在49层, 第一个鸡蛋碎了,第二个鸡蛋必须得从第一层楼找起。 因此这种策略最坏情况下的尝试次数是50次, 如果你决定采用这个策略, 那你针对题目的答案就是50
-
现在的目标是找到一个最优策略, 该策略所对应的最坏情况, 比其他策略所对应的最坏情况所需要尝试的次数都要少
-
明确了问题以后,首先 可以获得一个直觉性的推论, 那就是, 最优的策略必然是用第一个鸡蛋先进行跳跃性的尝试, 如果中间碎了, 就可以用第二个鸡蛋,在剩余的范围内,锁定答案
- 1.数学猜想反推法
-
假设题目所要求的策略存在,该策略可以保证在最坏情况下至多需要扔x次鸡蛋就可以找出临界楼层
-
此时可以将问题转化为:如果只允许扔x次鸡蛋 能确定的最高的楼层是多少。
-
现在思考, 第一次鸡蛋应该从哪个楼层扔下?
-
因为此时我们有两个鸡蛋, 为了在有限次数内检查尽可能高的楼层。 第一次扔的时候,我们会希望能尽可能高一些 如果鸡蛋没碎,我们就能排除掉越多的楼层。 但是很显然, 不能过高, 因为万一鸡蛋碎了, 第二个鸡蛋就只能从第一层开始尝试起。
-
那么第一次扔的楼层最高可以选几呢?
-
是x, 因为我们总要考虑最坏情况, 当我们从x层扔下的时候, 最坏的情况有可能是临界层为x-1, 此时就需要尝试x次,才能找到这个临界层。 如果第一次尝试的楼层比x大, 显然会导致最大尝试次数超过x , 不符合我们的假设。
-
-
回到我们的问题, 现在知道了在最大尝试次数为x次时, 最优策略第一次必然只能从x层扔下的推论后, 我们最大能检查的楼层是多少。
-
由于第一次从x层扔下时, 我们已经用掉了一次尝试机会, 此时只能再尝试x-1次。 这样我们可以第二次尝试的楼层数可以向上增加x-1层, 即为 x + (x-1).
-
如果再高,例如第二次我们从2x层扔下, 我们在最坏情况下(临界楼层为2x-1),需要尝试的次数会变成x+1, 不符合我们的假设。
-
这样依次类推, 第三次向上的增量就只能为 x-3, 第四次向上的增量就只能为 x-4, …, 最后一次向上的增量为 1
-
此时我们可以得出,如果只能尝试x次, 我们能检测的最高楼层为 x + ( x − 1 ) + ( x − 2 ) + ( x − 3 ) . . . . + 1 = ( x ∗ ( x + 1 ) ) / 2 x+ (x-1) +(x-2) + (x-3)....+1 = (x*(x+1))/2 x+(x−1)+(x−2)+(x−3)....+1=(x∗(x+1))/2
-
-
此时答案就出来了, 我们有100层楼, 则有
x*(x+1)/2 >=100, 解出来 x >= 14
-
2.动态规划穷举法
- 现在考虑动态规划解法, 根据上面的分析,其实可以发现, 第一次尝试的楼层, 会对最终的结果产生直接的影响。 现在 设 f(m, n) 为m个鸡蛋, n层楼时, 在最坏情况下的最少尝试次数。 如果第一次尝试从x层楼开始, 则 f ( m , n ) = 1 + m a x ( f ( m − 1 , x − 1 ) , f ( m , n − x ) ) f( m,n ) = 1+ max ( f(m-1, x-1) , f(m, n-x)) f(m,n)=1+max(f(m−1,x−1),f(m,n−x))
- f ( m − 1 , x − 1 ) f(m-1, x-1) f(m−1,x−1)对应的是第一次尝试鸡蛋被摔碎的情况
- f ( m , n − x ) f(m, n-x) f(m,n−x)对应的是 第一次尝试鸡蛋没被摔碎的情况
接下来,只需要编写动态规划程序, 将每一种x都尝试一次, 计算出最小的结果即可
这里给出Java代码
public class EggDropPuzzle {
public static void main(String[] args) {
int floors = 100;
int eggs = 2;
System.out.println(computeMinDropsInWorstCase(eggs, floors));
}
// A utility function to get maximum of two integers
static int max(int a, int b) { return (a > b)? a: b; }
private static int computeMinDropsInWorstCase(int eggs, int floors) {
int table[][]=new int[eggs+1][floors+1];
// boundary condition:
// if no floors or 1 floors, only need 0 trails or 1 trails
for (int i = 0; i <= eggs; i++) {
table[i][1] = 1;
table[i][0] = 0;
}
// if only one egg, f(1,k) = k
for (int j = 0; j <= floors; j++) {
table[1][j] = j;
}
// for the rest of cases
// f( eggs, floors) = 1+ Max(f( eggs-1 , floors-1), f( eggs, floors-x))
// x is the floor number we choose to drop for current attempt
// range of i = 1,2,.....,floors,
for(int i = 2; i <= eggs; i++)
{
for (int j = 2; j <= floors; j++) {
table[i][j] = Integer.MAX_VALUE; // so important
for (int floorTriedFirst = 1; floorTriedFirst <= j; floorTriedFirst++) {
int res = 1+max(table[i-1][floorTriedFirst-1],
table[i][j-floorTriedFirst]);
if(res < table[i][j])
{
table[i][j] = res;
}
}
}
}
return table[eggs][floors];
}
}