题目如下:
You are given two eggs, and access to a 100-storey building. Both eggs are identical. The aim is to find out the highest floor from which an egg will not break when dropped out of a window from that floor. If an egg is dropped and does not break, it is undamaged and can be dropped again. However, once an egg is broken, that is it for that egg.
If an egg breaks when dropped from floor n, then it would also have broken from any floor above that. If an egg survives a fall, then it will survive any fall shorter than that.
The question is: What strategy should you adopt to minimize the number egg drops it takes to find the solution?. (And what is the worst case for the number of drops it will take?)
- 问题一: 只有一个鸡蛋的时候, 如何测试
如果我们只有一个鸡蛋, 我们知道鸡蛋一旦碎了, 我们就没有鸡蛋了。 所以我们要找出这个使得鸡蛋恰好碎的critital floor楼层, 我们只能从第一层开始向上一层一层的找。
所以最坏的情况下, 我们需要扔100次才能找到(即要么在100层, 要么在100层也不会碎)。
- 问题二: 现在我们有两个鸡蛋, 我们该如何测试呢?
我们可以从第50层开始扔, 这样我们就可以把我们的问题的规模减为一半了。 有两种可能:
(1)蛋碎了, 我们知道critical floor在50层楼以下。 但是此时我们手里还剩下一只鸡蛋, 我们没有别的办法, 只能从第一层开始一层层的往上找。 最坏的情况下我们需要检查: 1(对应着第一个出师未捷的蛋) + 49 = 50次
(2) 蛋没有碎。 此时我们比较lucky, 然后继续用这个蛋进行二分测试。
无论如和, 我们去上述两种情况最坏的情况, 即50次了。
- 问题三: 能否做的更好
所以做的更好, 就是我们使得最坏情况下, 扔的次数是最小的。 我们可以选择按照如下的方式扔鸡蛋。
选择10floor的strategy。 也就是选择第一只鸡蛋:先从10层开始扔, 如果碎了, 就用第二个鸡蛋check 1-9层。 如果没有碎, 继续用这一个鸡蛋从20层开始扔, 一直进行下去。
最坏的情况下(即最大的扔鸡蛋的次数):
到90次的时候第一个鸡蛋还没有碎。 但是在100次的时候碎了。然后我们用第二个鸡蛋测试从91层开始, 一层层的测试。 我们的次数是: 10(第一个蛋) + 9 = 19次。
好的这个策略远好于第一个策略。
- 问题四: 还能做到更好吗?
我们可以选择minimization of maxmum regret的策略。
上述的办法是采用的等差数列的方式扔鸡蛋。 现在我们换个策略。 只要我们的第一个鸡蛋不碎, 我们就减少增加的楼层数进行扔鸡蛋。 这样就使得一旦我们的第一个鸡蛋碎了, 那么使用第二个鸡蛋测试所需要的次数的是递减的。 例如第一个鸡蛋在第一层就碎了与第一个鸡蛋在第二层碎了这两种可能对应的导致的第二个鸡蛋的测试次数是递减的。
如何找到第一个鸡蛋最开始的扔鸡蛋的层数。 我们按照如下方式计算出来:
(1)第一个鸡蛋从楼层n开始向下扔, 如果碎了, 使用第二个鸡蛋一层层检查前面(n-1)层楼。
(2)如果第一个鸡蛋没有碎, 那么接下来, 我们从2n - 1层开始往下扔。 也就是说此时我们又向上走了n -1层开始扔第一个鸡蛋。 如果碎了, 用第二个鸡蛋检查前面n -1 层。 没有碎, 继续向下扔第一个鸡蛋。。
(3)第一个鸡蛋没有碎, 在楼层n + n - 1 + n -2 = 3n -3处扔鸡蛋。
依次进行下去。
我们有如下公式:
n + (n-1) + (n-2) + (n-3) + (n-4) + … + 1 >= 100
于是得到:
n (n+1) / 2 >= 100
计算得到:
n = 13.651。
我们取ceiling, 得到n = 14.
所以我们测试的情况如下:
最坏的情况是我们扔了14次。 也就是我们第一次扔第一个蛋的时候, 这个悲催的家伙就碎了。 然后我们只能从一层开始向上, 逐层的用第二个蛋检查:
1 + 13 = 14。
下面我们使用动态规划求解这个题。
n个鸡蛋, k层楼。
一个问题要想搭上动态规划这趟高速列车, 那么这个问题的结构必须拥有如下两个优秀的特点。
(1)最优子结构
如果鸡蛋从x层向下扔的时候,会出现两个case:
– case 1: 鸡蛋碎了, 此时我们需要使用剩下的鸡蛋n - 1(假如我们有n 个鸡蛋)个鸡蛋去检查下面的x - 1层楼。
k ==> Number of floors
n ==> Number of Eggs
eggDrop(n, k) ==> Minimum number of trails needed to find the critical floor in worst case.
eggDrop(n, k) = 1 + min{max(eggDrop(n - 1, x - 1), eggDrop(n, k - x)): x in {1, 2, ..., k}}
(2) 重叠子问题
这个很容易看出来。
编程实现:
递归版本:
# include <cstdio>
# include <climits>
// A utility function to get maximum of two integers
int max(int a, int b) { return (a > b)? a: b; }
/* Function to get minimum number of trails needed in worst
case with n eggs and k floors */
int eggDrop(int n, int k)
{
// If there are no floors, then no trials needed. OR if there is
// one floor, one trial needed.
if (k == 1 || k == 0)
return k;
// We need k trials for one egg and k floors
if (n == 1)
return k;
int min = INT_MAX, x, res;
// Consider all droppings from 1st floor to kth floor and
// return the minimum of these values plus 1.
for (x = 1; x <= k; x++)
{
res = max(eggDrop(n-1, x-1), eggDrop(n, k-x));
if (res < min)
min = res;
}
return min + 1;
}
/* Driver program to test to pront printDups*/
int main()
{
int n = 2, k = 10;
printf ("\nMinimum number of trials in worst case with %d eggs and "
"%d floors is %d \n", n, k, eggDrop(n, k));
return 0;
}
动态规划:
# include <cstdio>
# include <climits>
// A utility function to get maximum of two integers
int max(int a, int b) { return (a > b)? a: b; }
/* Function to get minimum number of trails needed in worst
case with n eggs and k floors */
int eggDrop(int n, int k)
{
/* A 2D table where entery eggFloor[i][j] will represent minimum
number of trials needed for i eggs and j floors. */
int eggFloor[n+1][k+1];
int res;
int i, j, x;
// We need one trial for one floor and0 trials for 0 floors
for (i = 1; i <= n; i++)
{
eggFloor[i][1] = 1;
eggFloor[i][0] = 0;
}
// We always need j trials for one egg and j floors.
for (j = 1; j <= k; j++)
eggFloor[1][j] = j;
// Fill rest of the entries in table using optimal substructure
// property
for (i = 2; i <= n; i++)
{
for (j = 2; j <= k; j++)
{
eggFloor[i][j] = INT_MAX;
for (x = 1; x <= j; x++)
{
res = 1 + max(eggFloor[i-1][x-1], eggFloor[i][j-x]);
if (res < eggFloor[i][j])
eggFloor[i][j] = res;
}
}
}
// eggFloor[n][k] holds the result
return eggFloor[n][k];
}
/* Driver program to test to pront printDups*/
int main()
{
int n = 2, k = 36;
printf ("\nMinimum number of trials in worst case with %d eggs and "
"%d floors is %d \n", n, k, eggDrop(n, k));
return 0;
}
运行结果:
分析:
时间复杂度:O(nk^2)
空间复杂度:O(nk)