这个问题是一个印度朋友问我的。
已知有e个鸡蛋,f层楼。问题是尽可能少的尝试,找出扔出鸡蛋不破的最高的楼层。
最开始我的思路是,如果只有一个鸡蛋,f层楼,毫无疑问,只能从一楼到二楼到三楼...依次尝试,这样f层楼最坏的情况下要进行f次尝试。为什么要考虑最坏的情况,因为我们要确保该结果包含所有可能的情况,下面再详细说。然后我分析两个鸡蛋,f层楼。然后我发现,如果有两个鸡蛋,就可以从第2层楼开始扔:如果破了,在用剩下的1个鸡蛋检查1楼;如果没破,两个鸡蛋升至4楼检查同理。如果有3个鸡蛋,f层楼,可以从4楼开始检查:如果破了,2个鸡蛋退至2楼检查同上;如果没破,3个鸡蛋升至8楼检查同理。这样我能确保在耗尽所有鸡蛋的同时,发现最高的楼层。然后开始选择楼层是2的e-1次方,e是鸡蛋的数量,如果不破,继续上升2的e-1次方,破了下降2的e-2次方。。。虽然麻烦点儿,但是理论可行,代码实现也没有问题。
然后我很高兴的告诉印度朋友我解决了这个问题。印度朋友听完我的解释后说跟标准答案不一样。后来一经分析,他指出了这个方法的破绽,我太天真,这个方法是没问题,可以发现扔鸡蛋不破的最高楼层,但并不是最少的尝试。
然后告诉我这个题是有关动态规划。动态规划就是大量递归,但是存在重复计算,因此在开始递归的时候把结果保存在表里面,这样下次用到直接从表里面找不用再次计算,稍微快一点。
然后了解的官方的解释。假设有f层楼,e个鸡蛋。我们用一个表达式E(f, e)来表示这个扔鸡蛋问题的最优答案,即:在有n个鸡蛋f层楼的情况下,找出扔鸡蛋不破的最高楼层的最少尝试次数。然后开始考虑,首先从那一层楼开始扔,结果最佳?前面我尝试过从2的e-1次方层开始仍,但印度老哥已经证明这个方法有问题。所以,该从哪里扔?答案是不知道,至少我不知道。但是我知道计算机速度很快,我们完全可以枚举出所有的可能,然后从里面找出最优解。那就行了,假设先从k层楼往下扔,答案是多少?首先来到k层,扔出去鸡蛋,进行了1次尝试。然后需注意,扔出去鸡蛋的时候,会产生两种结果,一种是鸡蛋破了,一种鸡蛋没破。这两种情况实际只有一种情况会发生。但是我们应该考虑最坏的情况,也就是上界,确保这个上界能够cover两种情况。首先鸡蛋破了,我们知道还剩下e-1个鸡蛋,还知道最高的楼层肯定在k层以下,但不知道就那一层,所以这个时候用E(k-1, e-1)来表示后面最后的操作。如果没破,知道手里还有e个鸡蛋,而且最高楼层肯定在k层以上,只需检查以上的楼层,那就是E(f-k, e)来表示后面的最后操作。所以在k层开始仍的最优解是1+max(E(k-1, e-1), E(f-k, e))。然后因为我们有f层,所以k的值就是从1到f,也就是我们在先从那层楼开始仍这一问题上有f个选择,都可以用递归表达式表示出来。然后我们要做的就是从里面找出最小值,就是E(f, e)的最终答案。
熟悉递归的朋友肯定知道,我们必须知道base case的具体数,不然没法用递归。这里的base case有两个:如果有1个鸡蛋,f层楼,结果会是多少?是f;如果有e个鸡蛋1层楼,结果是多少?是1。然后就行了,有了base case,有了递归表达式,这个问题就解决了。
然后我是用了一个二维数组做记录,比纯递归能快多少没研究,不过总体效率不高。以后有空会去看一下官方的解法,目前没兴趣。基本跟二叉树的写法差不多。印度老哥号称深谙这“简单的 ”二叉树,结果连这个递归写不出来。不懂装懂的人还是挺多。
public class EggDrop
{
private int[][] resultSet;
private int floor;
private int egg;
public EggDrop(int floor, int egg)
{
resultSet = new int[floor+1][egg+1];
this.floor = floor;
this.egg = egg;
for(int i = 0; i < egg+1; i++)
{
resultSet[0][i] = 0;
resultSet[1][i] = 1;
}
for(int j = 0; j < floor+1; j++)
{
resultSet[j][0] = 0;
resultSet[j][1] = j;
}
}
public int minTry() { return minTry(floor, egg); }
private int minTry(int floor, int egg)
{
if(floor == 0 || egg == 0 || resultSet[floor][egg] != 0)
return resultSet[floor][egg];
int min = Integer.MAX_VALUE;
for(int i = 0; i < floor; i++)
{
int temp = 1 + Math.max(minTry(floor-i-1, egg-1), minTry(i, egg));
if(temp < min) min = temp;
}
resultSet[floor][egg] = min;
return min;
}
public static void main(String[] args)
{
int floor = 20;
int egg = 8;
EggDrop eggDrop = new EggDrop(floor, egg);
System.out.println(eggDrop.minTry());
int count = 0;
for(int i = 0; i < floor+1; i++)
{
for(int j = 0; j < egg+1; j++)
{
if(count < egg)
{
count++;
System.out.print(eggDrop.resultSet[i][j] + " ");
}
else
{
System.out.println(eggDrop.resultSet[i][j]);
count = 0;
}
}
}
}
}