• 扔鸡蛋问题


    标签: 算法


    初始问题:在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 + (x-1) + (x-2) + ... + 1 >= 100 ]

    [(x+1)*x/2 >= 100 ]

    得出(x=14)

    即测试序列为14,27,39,50,60,69,77,84,90,95,99,100。

  • 相关阅读:
    Java实现水仙花数
    CSS3属性选择器
    Word快捷键
    Java实现百钱买百鸡
    某专业人士给中国计算机专业学生的建议
    经典名言警句
    面试问题和思路
    情商
    Java注意的地方
    唯大英雄能真本色——Leo鉴书34
  • 原文地址:https://www.cnblogs.com/banyu/p/6618996.html
Copyright © 2020-2023  润新知