LeetCode刷题:887. Super Egg Drop

LeetCode刷题:887. Super Egg Drop

原题链接:https://leetcode.com/problems/super-egg-drop/

You are given K eggs, and you have access to a building with N floors from 1 to N. 

Each egg is identical in function, and if an egg breaks, you cannot drop it again.

You know that there exists a floor F with 0 <= F <= N such that any egg dropped at a floor higher than F will break, and any egg dropped at or below floor F will not break.

Each move, you may take an egg (if you have an unbroken one) and drop it from any floor X (with 1 <= X <= N). 

Your goal is to know with certainty what the value of F is.

What is the minimum number of moves that you need to know with certainty what F is, regardless of the initial value of F?

题目大意是:

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N  共有 N 层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

 

Example 1:(例1)

Input: K = 1, N = 2
Output: 2 
Explanation:
Drop the egg from floor 1.  If it breaks, we know with certainty that F = 0.
Otherwise, drop the egg from floor 2.  If it breaks, we know with certainty that F = 1.
If it didn't break, then we know with certainty F = 2.
Hence, we needed 2 moves in the worst case to know what F is with certainty. 

输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。


Example 2:

Input: K = 2, N = 6
Output: 3
Example 3:

Input: K = 3, N = 14
Output: 4


Note:

1 <= K <= 100
1 <= N <= 10000

题目分析

先把题目意思理解了。说实话,我反应慢,看了好久才稍稍理解了题目的意思。之后,在网上搜了关于这个问题的帖子,才更加了解了这个题目要求什么。(智商不够吧~)

题目的要求解读出来就是:最少需要多少次尝试, 才能保证在最坏的情况下,找到鸡蛋破碎的临界楼层 

明确问题

题目要求是考虑 在策略可能遭遇的 最坏情况下,尝试次数最少。 这个地方有些绕,但是必须搞清楚。

举个例子, 如果你用二分法,第一次把鸡蛋从50层扔下去, 最坏的情况是临界层在49层, 第一个鸡蛋碎了,第二个鸡蛋必须得从第一层楼找起。 因此这种策略最坏情况下的尝试次数是50次, 如果你决定采用这个策略, 那你针对题目的答案就是50。

现在的目标是找到一个最优策略, 该策略所对应的最坏情况, 比其他策略所对应的最坏情况所需要尝试的次数都要少。
 

我们假设有100层楼,我们开始扔鸡蛋,先从那一层开始尝试呢?

选择一:随机选择楼层 (失败的策略)

100层时的情况:假设从100层扔下第一个鸡蛋会发生什么?无非有两种选择:碎或者不碎。

  • 如果碎了,1个鸡蛋没有了,只剩下1个鸡蛋了,那么如果从99层扔下,碎了,就没有鸡蛋可扔了,1—98层的情况还不知道,任务没完成。如果恰巧没碎,算是运气,临界楼层就是99了。
  • 如果没碎,那么可以测试101层了。

看来这种想法不成立。也许你会说,如果从100层扔下碎了,那你可以从1—99层一层一层尝试,从1层,到2层,到3层,...

要记得,要找出最小的次数。

我们把100换成任意楼层,例如75层,80层,或者50层,按照上述方法都是不行的。

选择二:二分法 (失败的策略)

有人说我们采用二分法。

例如,第一个鸡蛋在50层扔下,如果碎了,第二个鸡蛋从1-49逐层试验。为什么要逐层试验呢?而不继续二分法呢?这个问题的答案显而易见,你只剩一个鸡蛋了。如果继续二分法测试,某一次,鸡蛋碎了,你还有很多剩余的楼层没有测试。而你没有鸡蛋了。

如果没碎,第一个鸡蛋在75层扔下,如果碎了,第二个鸡蛋从51-74逐层试验…你可以算算,需要多少次测试?

二分法也似乎不是好的方法。

选择三:等分法(失败的的略,但是比之前的两种选择稍稍好一些)

有人说,等分法。将100层楼,按照每10层进行等分划分。每一份就是10层楼。

首先,将鸡蛋从第10层楼开始扔。那么结果有两种可能:

  • 情况1:如果碎了,说明临界楼层在1到10之间,但现在只剩下一个鸡蛋了,只能从第一层一直到第10层。
  • 情况2:如果没有碎,接下来从第20层扔鸡蛋。

该方法的思路是,用一个鸡蛋来试探,找到临界楼层的大致范围[1~10]、[11-20]….[91-100]。然后用另一个鸡蛋在大致范围内找出精确楼层。该方法的最坏次数是:18次。

看来尝试的次数减少了很多。还有没有更好地选择?

选择四:数学猜测法(设定X)

假设题目所要求的策略存在,该策略可以保证在最坏情况下至多需要扔 x 次鸡蛋就可以找出临界楼层。

此时可以将问题转化为:如果只允许扔 x 次鸡蛋能确定的最高的楼层是多少。现在思考, 第一次鸡蛋应该从哪个楼层扔下?

因为我们有两个鸡蛋, 为了在有限次数内检查尽可能高的楼层。 第一次扔的时候,我们会希望能尽可能高一些。如果鸡蛋没碎,我们就能排除掉越多的楼层。 但是很显然,又不能过高, 因为万一鸡蛋碎了,第二个鸡蛋就只能从第1层开始逐层尝试。
那么第一次扔的楼层最高可以选第几层呢?

答案就是 x 。 

因为我们总要考虑最坏情况, 当我们从 x 层扔下的时候, 最坏的情况有:

  • 可能是临界层为 x-1, 此时就需要尝试 x 次,才能找到这个临界层。
  • 如果第1次尝试的楼层比 x 大,显然会导致最大尝试次数超过x , 不符合我们的假设(我们的假设是尝试 x 次)。

现在假设了在最大尝试次数为x次时, 最优策略第1次必然只能从 x 层往下取值后, 我们最大能检查的楼层是多少?

由于第1次从 x 层扔下时, 我们已经用掉了1次尝试机会, 此时只能再尝试 x-1 次。 这样我们可以第二次尝试的楼层数可以向上增加 x-1层, 即为 x + (x-1)。
如果再高,例如,第2次我们从 2x 层扔下, 我们在最坏情况下(临界楼层为 2x-1),需要尝试的次数会变成 x+1, 不符合我们的假设。

这样依次类推, 第3次向上的增量就只能为 x-3, 第4次向上的增量就只能为 x-4, …….., 最后一次向上的增量为 1。

此时可以得出,如果只能尝试 x 次, 能检测的最高楼层为:

x + (x−1) + (x−2) + (x−3)....+1

等价于:(x∗(x+1)) / 2x+(x−1)+(x−2)+(x−3)....+1

等价于:(x∗(x+1)) / 2

此时答案就出来了, 我们有100层楼, 则有

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

解出来 x >= 14

即需要测试 14 次。

借鉴了别人的想法,自己想了很久才理解了。

 

算法分析

N 个eggs,K 代表楼层

递归算法:试着从每层楼,从1到K扔下一个鸡蛋,并计算出最坏情况下需要扔下的最小数量。

  • Eggs — 1,Floors—x:从1楼开始扔鸡蛋,如果鸡蛋没有破,那么再从2楼扔,依次类推。所以,最坏的情况是,当x次以后可以找到可以扔破鸡蛋的楼层。
  • Floors = 0:不用试
  • Floors = 1:需要尝试
  • 剩余的情况:如果鸡蛋从第x层扔下,只能存在两种情况:破或者不破
  • 如果鸡蛋破了,check楼层是否小于x;所以问题是减少了 n-1个鸡蛋和 x-1层楼层
  • 如果鸡蛋没破,check楼层是否大于x,并剩余了n个鸡蛋。所以问题是减少了n个鸡蛋和k-x层楼层

没有思路,先看看这里的解释:https://brilliant.org/wiki/egg-dropping/

 

算法设计

public int getDrops(int eggs, int floors) {
		// base case 1:
		// if floors = 0 then no drops are required OR floors = 1 then 1 drop is
		// required
		if (floors == 0 || floors == 1)
			return floors;

		// base case 2:
		// if only one egg is there then drops = floors
		if (eggs == 1)
			return floors;

		int minimumDrops = Integer.MAX_VALUE, tempResult;
		// check dropping from all the floors 1 to floors and pick the minimum among
		// those.
		// for every drop there are 2 scenarios - a) either egg will break b) egg will
		// not break
		for (int i = 1; i <= floors; i++) {
			// for the worst case pick the maximum among a) and b)
			tempResult = Math.max(getDrops(eggs - 1, i - 1), getDrops(eggs, floors - i));
			minimumDrops = Math.min(tempResult, minimumDrops);
		}
		return minimumDrops + 1;
	}

这个算法提交之后,Time Limit Exceeded!

LeetCode刷题:887. Super Egg Drop


 采用动态规划算法

public int superEggDrop(int K, int N) {
       int dp[] = new int [K+1],m=0;
         for(m=0;dp[K]<N;++m){
             for(int k = K;k>0;--k)
             dp[k]+=dp[k-1]+1;
         }
         return m;
}

这个算法提交之后,AC了。

LeetCode刷题:887. Super Egg Drop 

完整测试代码如下:

package com.bean.algorithm.basic;

public class EggDropDP {
	
	 public int superEggDrop(int K, int N) {
	       int dp[] = new int [K+1],m=0;
	         for(m=0;dp[K]<N;++m){
	             for(int k = K;k>0;--k)
	             dp[k]+=dp[k-1]+1;
	         }
	         return m;
	    }

	public static void main(String[] args) {
		EggDropDP eggDropDP = new EggDropDP();
		int eggs = 2;
		int floors = 10;
		System.out.printf("(DP) Minimum number of drops required in worst case with eggs: " + eggs
				+ " and floors:" + floors + " is: " + eggDropDP.superEggDrop(eggs, floors));
	}
}

另外一种AC的JAVA代码,从LeetCode上摘抄的,学习一下!

	public int superEggDrop(int K, int N) {
		if (N <= 0) {
			return 1;
		}
		if (K <= 1) {
			return N;
		}
		List<List<Integer>> nodes = new ArrayList<>();
		// Initialize DP table for K = 1
		for (int k = 0; k < K - 1; k++) {
			List<Integer> list = new ArrayList<>();
			list.add(1);
			nodes.add(list);
		}
		int level = 0; // Current level
		int sum = 1; // Total number of nodes in the K-Tree up to current level
		while (sum < N) {
			nodes.get(0).add(nodes.get(0).get(level) + 1);
			for (int k = 1; k < K - 1; k++) {
				nodes.get(k).add(nodes.get(k - 1).get(level) + nodes.get(k).get(level));
			}
			level++;
			sum += nodes.get(K - 2).get(level);
		}
		return level + 1;

	}

提交AC

LeetCode刷题:887. Super Egg Drop


动态规划思路

第一次尝试的楼层, 会对最终的结果产生直接的影响。

现在 设 f(m, n) 为m个鸡蛋, n层楼时, 在最坏情况下的最少尝试次数。

如果第一次尝试从 x 层楼开始, 则 f(m,n)=1+max(f(m−1,x−1),f(m,n−x))
f(m−1,x−1) 对应的是第一次尝试鸡蛋被摔碎的情况
f(m,n−x) 对应的是第一次尝试鸡蛋没被摔碎的情况
接下来,只需要编写动态规划程序, 将每一种 x 都尝试一次, 计算出最小的结果即可。

package com.bean.algorithm.basic;

public class EggDropDP2 {

	public static void main(String[] args) {
		int floors = 100;
		int eggs = 2;

		System.out.println(superEggDrop(eggs, floors));
	}


	private static int superEggDrop(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 + Math.max(table[i - 1][floorTriedFirst - 1], table[i][j - floorTriedFirst]);
					if (res < table[i][j]) {
						table[i][j] = res;
					}
				}

			}
		}

		return table[eggs][floors];
	}

}

这个算法,可以得到正确的答案。

但是在LeetCode上提交,仍然是 Time Limit Exceeded!