扔鸡蛋问题
标签: 算法
初始问题:在100层楼里,给定2个鸡蛋,在安全楼层以上的楼扔鸡蛋会碎。设计一种方案,测试哪层楼是最高安全楼层,要求测试次数最少。
思路:这是典型的动态规划问题。
假设\(f[n]\)表示从\(n\)层楼找到摔鸡蛋不碎安全楼层的最少判断次数。
假设第一个鸡蛋第一次从第\(i\)层扔下,
如果碎了,说明安全楼层低于\(i\)。剩一个鸡蛋,为确定\(i-1\)下面楼层中的安全楼层,必须从第一层挨着试直到\(i-1\)层,还需要扔\(i-1\)次鸡蛋;
如果不碎的话,说明安全楼层高于\(i\)。问题转化为,在以第\(i+1\)层为地面的\(n-i\)层楼里找安全楼层,还有两个鸡蛋,即还需要扔\(f[n-i]\)次鸡蛋。
因此,在上述两种情况中,最差情况是扔\(1 + max(i-1,f[n-i])\)次鸡蛋。
状态转移方程:
$$f[n] = min{ 1+max(i-1,f[n-i]) | i=1..n }$$
初始条件:
$$f[0]=0, f[1]=1$$
代码实现
//@num 层数
public static int dropEgg(int num){
int []dp = new int[num + 1];
dp[0] = 0;
dp[1] = 1;
for (int n = 2; n <= num; n++) {
int min = Integer.MAX_VALUE;
for (int i = 1; i <= n; i++) {
int max = Math.max(i - 1, dp[n - i]) + 1;
if(max < min) min = max;
}
dp[n] = min;
}
return dp[num];
}
问题扩展:\(n\)层楼与\(m\)个鸡蛋的问题。
假设\(f[n,m]\)表示\(n\)层楼、\(m\)个鸡蛋情况下,找到摔鸡蛋不碎的安全楼层的最少判断次数。
假设一个鸡蛋从第\(i\)层扔下,
如果碎了,还剩\(m-1\)个鸡蛋,为确定下面楼层中的安全位置,还需要扔\(f[i-1,m-1]\)次鸡蛋;
如果不碎,上面还有\(n-i\)层,还需要扔\(f[n-i,m]\)次鸡蛋。
因此,在上述两种情况中,最差情况是扔\(1 + max(f[i-1,m-1],f[n-i,m])\)次鸡蛋。
状态转移方程:
$$f[n,m] = min{ 1+max(f[i-1,m-1], f[n-i,m]) | i=1..n }$$
初始条件:
$$f[i,0]=0,f[i,1]=i$$
//@num 层数
//@egg 鸡蛋数
public static int dropEgg(int num, int egg) {
int[][] dp = new int[num + 1][egg + 1];
for (int i = 0; i < dp.length; i++) {
dp[i][0] = 0;
dp[i][1] = i;
}
for (int i = 0; i < dp[0].length; i++) {
dp[0][i] = 0;
dp[1][i] = 1;
}
for (int n = 2; n <= num; n++) {
for (int m = 2; m <= egg; m++) {
int min = num;
for (int i = 1; i <= n; i++) {
int max = Math.max(dp[i - 1][m - 1], dp[n - i][m]) + 1;
if (max < min)
min = max;
}
dp[n][m] = min;
}
}
return dp[num][egg];
}
回到初始问题,怎么设计这个扔鸡蛋方案呢?
我们先假设最坏情况下,鸡蛋下落次数为\(x\),即我们为了找出\(N\),一共用鸡蛋做了\(x\)次的实验。
那么,我们第一次应该在哪层楼往下扔鸡蛋呢?
先让我们假设第一次是在第\(y\)层楼扔的鸡蛋,如果第一个鸡蛋在第一次扔就碎了,我们就只剩下一个鸡蛋,要用它准确地找出\(N\),只能从第一层向上,一层一层的往上测试,直到它摔坏为止,答案就出来了。
由于第一个鸡蛋在第\(y\)层就摔破了,所以最坏的情况是第二个鸡蛋要把第\(1\)到第\(y-1\)层的楼都测试一遍才得出结果。
这样一来测试次数是\(1+(y-1)=x\),即第一次测试要在第x层。
那如果第一次测试鸡蛋没摔破呢,那\(N\)肯定要比\(x\)大,要继续往上找,需要在哪一层扔呢?
如果第一个鸡蛋在第二次测试中摔破了,那么第二个鸡蛋的测试次数就只剩下\(x-2\)次了(第一个鸡蛋用了2次)。
这样一来,第二次扔鸡蛋的楼层和第一次扔鸡蛋的楼层之间就隔着\(x-2\)层。我们再回过头来看一看,第一次扔鸡蛋的楼层在第\(x\)层,第1层到第\(x\)层间共\(x\)层;第1次扔鸡蛋的楼层到第2次扔鸡蛋的楼层间共有\(x-1\)层(包含第2次扔鸡蛋的那一层),同理继续往下,我们可以得出,第2次扔鸡蛋的楼层到第3次扔鸡蛋的楼层间共有\(x-2\)层……最后把这些互不包含的区间数加起来,应该大于等于总共的楼层数量100,即
得出\(x=14\)。
即测试序列为14,27,39,50,60,69,77,84,90,95,99,100。