扔鸡蛋问题具体解释(Egg Dropping Puzzle)

经典的动态规划问题,题设是这种:

假设你有2颗鸡蛋,和一栋36层高的楼,如今你想知道在哪一层楼之下,鸡蛋不会被摔碎,应该怎样用最少的測试次数对于不论什么答案楼层都可以使问题得到解决。

  • 假设你从某一层楼扔下鸡蛋,它没有碎,则这个鸡蛋你能够继续用
  • 假设这个鸡蛋摔碎了,则你能够用来測试的鸡蛋降低一个
  • 全部鸡蛋的质量同样(都会在同一楼层以上摔碎)
  • 对于一个鸡蛋,假设其在楼层i扔下的时候摔碎了,对于不论什么不小于i的楼层,这个鸡蛋都会被摔碎
  • 假设在楼层i扔下的时候没有摔碎,则对于不论什么不大于i的楼层,这颗鸡蛋也不会摔碎
  • 从第1层扔下,鸡蛋不一定完善,从第36层扔下,鸡蛋也不一定会摔碎。

实际上,我们的终极目的是要找出连续的两层楼i,i+1在楼层i鸡蛋没有摔碎,在楼层i+1鸡蛋碎了,问题的关键之处在于,測试之前,你并不知道鸡蛋会在哪一层摔碎,你须要找到的是一种測试方案,这样的測试方案,不管鸡蛋会在哪层被摔碎,都至多仅仅须要m次測试,在全部这些測试方案中,m的值最小。

对于仅仅有1颗鸡蛋的情况,我们别无选择,仅仅能从1楼開始,逐层向上測试,直到第i层鸡蛋摔碎为止,这时我们知道,会让鸡蛋摔碎的楼层就是i(或者直到顶层,鸡蛋也没有被摔碎),其它的測试方案均不可行,由于假设第1次測试是在不论什么i>1的楼层扔下鸡蛋,假设鸡蛋碎了,你就无法确定,i-1层是否也会令鸡蛋摔碎。所以对于1颗鸡蛋而言,最坏的情况是使鸡蛋摔碎的楼层数i>=36,此时,我们须要測试每一个楼层,总共36次,才干找到终于结果,所以1颗鸡蛋一定能解决36层楼问题的最少測试次数是36.

对于2个鸡蛋,36层楼的情况,你可能会考虑先在第18层扔一颗,假设这颗碎了,则你从第1层,到第17层,依次用第2颗鸡蛋測试,直到找出答案。假设第1颗鸡蛋没碎,则你依旧能够用第1颗鸡蛋在27层进行測试,假设碎了,在第19~26层,用第2颗鸡蛋依次測试,假设没碎,则用第1颗鸡蛋在32层进行測试,……,如此进行(有点类似于二分查找)。这个解决方式的最坏情况出如今结果是第17/18层时,此时,你须要測试18次才干找到终于答案,所以该方案,解决36层楼问题的測试次数是18.

相较于1颗鸡蛋解决36层楼问题,測试次数实现了减半,可是18并非确保解决2颗鸡蛋,36层楼问题的最小值(实际的最小值是8).

我们能够将这种问题简记为W(n,k),当中n代表可用于測试的鸡蛋数,k代表被測试的楼层数。对于问题W(2,36)我们能够如此考虑,将第1颗鸡蛋,在第i层扔下(i能够为1~k的随意值),假设碎了,则我们须要用第2颗鸡蛋,解决从第1层到第i-1层楼的子问题W(1,i-1),假设这颗鸡蛋没碎,则我们须要用这两颗鸡蛋,解决从i+1层到第36层的子问题W(2,36-i),解决这两个问题,能够分别得到一个尝试次数p,q,我们取这两个次数中的较大者(假设是p),与第1次在i层运行測试的这1次相加,则p+1就是第一次将鸡蛋仍在i层来解决W(2,36)所需的最少測试次数次数ti。对于36层楼的问题,第一次,我们能够把鸡蛋仍在36层中的不论什么一层,所以能够得到36中解决方式的測试次数T{t1,t2,t3,……,t36},在这些结果中,我们选取最小的ti,使得对于集合T中随意的值tj(1<=j<=36,j!=i),都有ti<=tj,则ti就是这个问题的答案。用公式来描写叙述就是W(n,
k) = 1 + min{max(W(n -1, x -1), W(n, k - x))}, x in {2, 3, ……,k},当中x是第一次的測试的楼层位置。

当中W(1,k) = k(相当于1颗鸡蛋測试k层楼问题),W(0,k) = 0,W(n, 0) = 0

所以在计算W(2,36)之前,我们需先计算出全部W(1,0),……,W(1,36),W(2,0),……,W(2,35)这些的值,能够用递推的方法实现,代码例如以下:

unsigned int DroppingEggsPuzzle(unsigned int eggs, unsigned int floors)
{
	unsigned int i, j, k, t, max;

	unsigned int temp[eggs + 1][floors + 1];

	for(i = 0; i < floors + 1; ++i)
	{
		temp[0][i] = 0;
		temp[1][i] = i;
	}

	for(i = 2; i < eggs + 1; ++i)
	{
		temp[i][0] = 0;
		temp[i][1] = 1;
	}

	for(i = 2; i < eggs + 1; ++i)
	{
		for(j = 2; j < floors + 1; ++j)
		{
			for(k = 1, max = UINT_MAX; k < j; ++k)
			{
				t = temp[i][j - k] > temp[i - 1][k -1] ?  temp[i][j - k] : temp[i - 1][k -1];

				if(max > t)
				{
					max = t;
				}
			}

			temp[i][j] = max + 1;
		}
	}

	return temp[eggs][floors];
}

该算法的空间复杂度是O(nk),时间复杂度是O(nk^2),对于规模较大的问题,不管是空间还是时间复杂度都非常可观。

这个算法能够计算出W(2,36)问题的最少測试次数是8,可是却不能给出用2颗鸡蛋解决36层楼问题的详细方案,这里我就给出一个測试方案:

  • 用第一颗鸡蛋分别在8,15,21,26,30,33,35层进行測试
  • 假设鸡蛋在某一层碎了(比如26层),则在前一測试点由下到上依次測试,比如(22,23,24,25),直到找到满足条件的楼层为止
  • 假设鸡蛋在第35层的測试中也没碎,则用该鸡蛋在第36层再測试一次

该方案能够保证,不管满足条件的楼层是多少,都能够在最多8次測试之后找到答案,比如目标楼层为28时,该方案的測试顺序为8,15,21,26,30,27,28,总共測试7次,有兴趣的读者能够尝试一下其它情况。

该方案解决W(2,36)问题比較优雅,可是却暗藏一个非常大的玄机,那就是一般我们见到的这个问题的题面,往往是W(2,15),W(2,36),不知道读者考虑过没有,为什么非让我们计算2颗鸡蛋測试36层楼的情况,而不是35层或者37层?以下是用之前的算法解决W(4,50)问题的递推结果表格(当中,行代表楼层数1~50,列代表鸡蛋数1~4),我们会发现,W(2,36)=8,W(2,37) = 9,那么是不是用2颗鸡蛋測试8次,最多仅仅能解决36层楼问题,对于37层就无能为力了呢?

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
1 2 2 3 3 3 4 4 4 4 5 5 5 5 5 6 6 6 6 6 6 7 7 7 7 7 7 7 8 8 8 8 8 8 8 8 9 9 9 9 9 9 9 9 9 10 10 10 10 10
1 2 2 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7
1 2 2 3 3 3 3 4 4 4 4 4 4 4 4 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6

这里引出了一个问题:n个鸡蛋,測试m次(简记为D(n,m)),最大能够解决几层楼的问题,通过对递推结果表格的观察,我们能够得到例如以下结论

  1. D(1,m) = m
  2. D(n,n) = 2^n - 1
  3. D(n,m){m <= n} = D(m,m)

对于第二点,以D(4,4)为例,我们第1次在8楼扔下鸡蛋,假设碎了,则第二次在4楼扔下鸡蛋,否则在12楼扔下鸡蛋,对于在4楼扔下鸡蛋的情况,之后能够分别在2楼或者6楼扔下鸡蛋,如此进行,就能够找到答案楼层,方法与二分查找一样。比如答案楼层是5的情况,測试序列为8,4,6,5。

对于第三点,假设有5个鸡蛋让你測试3次,即使三次測试鸡蛋都碎了,剩下的2个鸡蛋也派不上用场,所以D(5,3) = D(3,3)

发现这些关系之后,我们似乎找到解决n个鸡蛋測试m次最大能够解决楼层数的方法。对于D(n,m){n < m}而言,对于其能够測试的最大楼层数k,我们能够构造这种场景,将第一颗鸡蛋仍在楼层i,使得第i + 1层到第k层是D(n,m-1)能够解决的最大楼层数,第1层到第i - 1层是D(n-1,m-1)能够解决的最大楼层数,由此得到递推关系D(n,m) = D(n -1,m-1) + 1 + D(n,m-1),然后对D(n,m-1),D(n-1,m-1)再依照上述公式分解,直到得出刚才所列的三种可计算情况(n
= 1,或者m <= n)为止,再进行回溯累加,就能够得到D(n,m)的值,代码例如以下:

unsigned int DroppingMax(unsigned int eggs, unsigned times)
{
	if(eggs == 1)
	{
		return times;
	}

	if(eggs >= times)
	{
		return (unsigned int)pow(2, times) - 1;
	}

	return DroppingMax(eggs, times -1) + DroppingMax(eggs -1, times - 1) + 1;
}

依据此算法,我们能够得出D(2,5)=15,D(2,8)=36,也就是说,2个鸡蛋測试5次最多能够解决15层楼的问题,測试8次最多能够解决36层楼的问题。可见,出这个题的人并非随便找两个楼层数陪咱们玩玩,而是对此问题认真研读后的结果。有了此利器之后,我们解决扔鸡蛋问题的的方法将得到大幅简化,对于n个鸡蛋解决k层楼的问题我们仅仅需找到这种值m,使得D(n,m-1)<k<=D(n,m),代码例如以下

unsigned int DroppingEggsPuzzle2(unsigned int eggs, unsigned int floors)
{
	unsigned int times = 1;

	while(DroppingMax(eggs, times) < floors)
	{
		++times;
	}

	return times;
}

该算法的时间和空间复杂度不太好分析,但都要好于传统的DP算法,有兴趣的读者能够推敲一下,在我的机器上測试10个鸡蛋,5000层楼的情况,第二个方法比第一个要快10万倍!注意到算法2也是一个动态规划问题,所以能够用一个n*m的矩阵来保存计算过程中的中间结果,算法的效率还能够得到非常大提升!

无论是算法1,还是算法2,都没有给出用n个鸡蛋怎样通过m次測试,解决k层楼的问题,对此我依据算法2给出一个思路。对于满足条件D(n,m-1)<k<=D(n,m),的測试次数m,将D(n,m),和D(n,m-1)依照D(n,m) = D(n -1,m-1) + 1 + D(n,m-1) 的方式展开,这里展开过程中要严格依照公式中各迭代的顺序,也就是说先是D(n-1,m-1),然后是1,然后是D(n,m-1),顺序不能乱,然后比較两结果,比如

D(3,5) = D(1,3)+1[2]+D(1,2)+1[3]+D(2,2)+1[1]+D(1,2)+1[3]+D(2,2)+1[2]+D(3,3)

D(3,4) =                                     D(1,2)+1[2]+D(2,2)+1[1]+D(3,3)

这当中每一个单独的1,都代表一次独立測试,这些1后面中的中括号代表其是第几次独立測试,与其从公式中分离出来的时机相关,最早分离出来的1,其值就是[1],第二次分离出来的1,其值就是[2],这些1的目的就是把k层楼分解为若干个可直接计算的子部分。我们取出两者不同的部分D(1,3)+1[2]+D(1,2)+1[3]+D(2,2)+1[1],这部分表示通过添加了一次測试,我们所获得的额外的探測能力,通过改造这部,使得这部分的和等于k-D(n,m-1),然后将改装部分与两者的同样部分结合,形成新的结果,这些结果从前到后,相应着楼层从下到上的測试方案

上例中我们知道D(3,4)=14, D(3,5)=25,对于14 < k <= 25,我们用k减去14得到须要构造的值,尽量保留右側的算式,仅仅改变最左側的算式,比如对于k = 15,不同部分能够用1替换,对于k = 16能够用D(1,1)+1替换,对于k = 18能够用D(2,2)+1替换,对于k = 21能够用D(1,2)+1+D(2,2)+1替换。以21为例,我们将改造结果和D(3,4),D(3,5)的同样部分结合,形成

D(1,2)+1[2]+D(2,2)+1[1]+D(1,2)+1[3]+D(2,2)+1[2]+D(3,3)

以下用图说明怎样用3个鸡蛋測试5次,解决21层楼问题,这里的规律是,对于独立的測试而言,假设測试摔碎,则向低楼层运行兴许的測试,假设没有摔碎,则向高楼层运行兴许的測试,当中的括号表示该測试运行的楼层/楼层区间。

实际上,对于D(n,m-1)<k<D(n,m)的情况,满足条件的測试方案不止一种。

后记:

  • 这是国外牛人的一篇文章,对于扔鸡蛋问题的理论分析,让人叹服,有兴趣的读者能够看一看,进一步深挖这个问题
  • 对于算法2的几个前提,我没能给出数学上的证明,那篇国外大牛的文章里面有涉及,只是那篇文章太长了,我没有看完。
  • 依据D(n,m)的递推关系,或许能够得到这个数列的通项公式。
  • 扔鸡蛋问题,与其说是一个动态规划问题,不如说是一个在特定场景下的数学问题,程序在该问题中很多其它价值在于验证结论。
时间: 2024-07-30 20:41:08

扔鸡蛋问题具体解释(Egg Dropping Puzzle)的相关文章

扔鸡蛋问题详解(Egg Dropping Puzzle)

http://blog.csdn.net/joylnwang/article/details/6769160 经典的动态规划问题,题设是这样的:如果你有2颗鸡蛋,和一栋36层高的楼,现在你想知道在哪一层楼之下,鸡蛋不会被摔碎,应该如何用最少的测试次数对于任何答案楼层都能够使问题得到解决. 如果你从某一层楼扔下鸡蛋,它没有碎,则这个鸡蛋你可以继续用 如果这个鸡蛋摔碎了,则你可以用来测试的鸡蛋减少一个 所有鸡蛋的质量相同(都会在同一楼层以上摔碎) 对于一个鸡蛋,如果其在楼层i扔下的时候摔碎了,对于任

扔了一晚上的鸡蛋,找出来了第一次扔鸡蛋的最佳地点

http://blog.csdn.net/joylnwang/article/details/6769160 先给大家看看大神写的扔鸡蛋问题.所有的原理,DP实现都在,不赘述.大家好好学习,天天向上. 我想分享的是: 第一次扔地点的最佳地点的递归找法:(自己的测试都过了,希望大家都来查错:想按照大神的意思,找出所有的分界点,即找出扔各个鸡蛋的碎或没碎太困难啦,啥时候想到再分享给大家) 贴代码解释: #include<cstdio> #include<string> #include

POJ 3783 Balls(扔鸡蛋问题——DP动态规划)

传送门 Balls Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 892 Accepted: 588 Description The classic Two Glass Balls brain-teaser is often posed as: "Given two identical glass spheres, you would like to determine the lowest floor in a 100-s

[CareerCup] 6.5 Drop Eggs 扔鸡蛋问题

6.5 There is a building of 100 floors. If an egg drops from the Nth floor or above, it will break. If it's dropped from any floor below, it will not break. You're given two eggs. Find N, while minimizing the number of drops for the worst case 这道题说有10

Eggs Dropping puzzle(2 eggs, 100 floors)

题目如下: 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 brea

扔鸡蛋

Description Jzj要来做一个经典的实验:测试鸡蛋壳的坚硬程度. Jzj正好处于N层高的摩天大楼中,所以通过从某一楼层向下扔鸡蛋来测试鸡蛋壳的坚硬程度. Jzj有M个鸡蛋,所有的鸡蛋硬度都一样.如果鸡蛋从第L层摔下去没有碎,而在L+1层摔碎了,那么称鸡蛋的硬度是L.嗯,反正jzj不喜欢吃鸡蛋,所以不用担心浪费的问题.大楼共有N层高,如果在N层还没有摔碎,就认为硬度是N:如果在第1层就碎了,硬度为0. 虽然jzj不喜欢吃鸡蛋,但是jzj的好朋友yk特别喜欢吃鸡蛋.为了帮好朋友保护可怜的小

大楼扔鸡蛋问题(动态规划)

题目链接:poj 3783 题意分析: 经典题,小白书上的一道例题,4+2出了这道原题,我愣是以为是数学题,最后也没做出来.题意是这样的,给你N个鸡蛋(硬度一样),让你测鸡蛋的硬度,测量的方法就是从某栋M层的楼的某一层X上把鸡蛋扔下来,如果鸡蛋碎了,代表他的强度小于X:如果没碎,则强度大于等于X.我们要做的就是不断的从楼上把鸡蛋扔下来,直到找到某一层楼X,从这一层楼扔下来鸡蛋不碎掉,从X+1层扔下来鸡蛋碎掉,那么鸡蛋的强度就是X.如果在M层扔下来鸡蛋也不碎掉,那么鸡蛋的强度为M.问题是,用N个鸡

Cracking the coding interview 智力题之-扔鸡蛋问题

问题: 解法: 这个问题算是一个智力题,没看过相关题目很难第一时间想出来.其中的"最差情况平衡"思路值得学习. 原文地址:https://www.cnblogs.com/J1ac/p/9248978.html

动态规划法(六)鸡蛋掉落问题(二)

??上次我们讲到,我们的主人公丁丁由于用动态规划法解决了鸡蛋掉落问题(egg dropping problem)而获得了当地科学家的赏识.这不,正当丁丁还沉浸在解决问题的喜悦中,科学家又给丁丁出了一个难题: 假设有n个鸡蛋和d次尝试机会,那么,最多能探索多少层楼? 这无疑是鸡蛋问题的翻版,因为这两个问题实在太像了.丁丁没有犹豫,立马按照之前的想法开始思考: ??用\(f(d, n)\)表示该问题的解.假设从k层楼扔下鸡蛋(k足够大),若鸡蛋碎了,则剩下n-1个鸡蛋,d-1次尝试机会,最多能向下探