扔鸡蛋问题具体解释(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)),最大能够解决几层楼的问题,通过对递推结果表格的观察,我们能够得到例如以下结论
- D(1,m) = m
- D(n,n) = 2^n - 1
- 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)的递推关系,或许能够得到这个数列的通项公式。
- 扔鸡蛋问题,与其说是一个动态规划问题,不如说是一个在特定场景下的数学问题,程序在该问题中很多其它价值在于验证结论。
扔鸡蛋问题具体解释(Egg Dropping Puzzle)的更多相关文章
- 扔鸡蛋问题详解(Egg Dropping Puzzle)
http://blog.csdn.net/joylnwang/article/details/6769160 经典的动态规划问题,题设是这样的:如果你有2颗鸡蛋,和一栋36层高的楼,现在你想知道在哪一 ...
- Egg Dropping Puzzle问题的分析
首先,基本问题是这样:You are given two eggs, and access to a 100-storey building. The aim is to find out the h ...
- Egg Dropping Puzzle
The Two Egg Problem 曾经是Google的一道经典题. 题意:有一个百层高楼,鸡蛋在\(L\)层及以下扔都不碎,在\(L\)层以上都会碎.现在某人有\(k\)个鸡蛋,问在最坏情况下, ...
- 动态规划法(六)鸡蛋掉落问题(一)(egg dropping problem)
继续讲故事~~ 这天,丁丁正走在路上,欣赏着路边迷人的城市风景,突然发现前面的大楼前围了一波吃瓜群众.他好奇地凑上前去,想一探究竟,看看到底发生了什么事情. 原来本市的一位小有名气的科学家 ...
- Coursera Algorithms week1 算法分析 练习测验: Egg drop 扔鸡蛋问题
题目原文: Suppose that you have an n-story building (with floors 1 through n) and plenty of eggs. An egg ...
- Leetcode 887 Super Egg Drop(扔鸡蛋) DP
这是经典的扔鸡蛋的题目. 同事说以前在uva上见过,不过是扔气球.题意如下: 题意: 你有K个鸡蛋,在一栋N层高的建筑上,被要求测试鸡蛋最少在哪一层正好被摔坏. 你只能用没摔坏的鸡蛋测试.如果一个鸡蛋 ...
- [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. I ...
- Balls(扔鸡蛋问题)
4554 BallsThe classic Two Glass Balls brain-teaser is often posed as:“Given two identical glass sphe ...
- Google面试题-高楼扔鸡蛋问题
本文由 @lonelyrains 出品.转载请注明出处. 文章链接: http://blog.csdn.net/lonelyrains/article/details/46428569 高楼扔鸡蛋问 ...
随机推荐
- 从源代码角度分析ViewStub 疑问与原理
一.提出疑问 ViewStub比較简单.之前文章都提及到<Android 性能优化 三 布局优化ViewStub标签的使用>.可是在使用过程中有一个疑惑,究竟是ViewStub上设 ...
- HOJ 2245 浮游三角胞(数学啊 )
题目链接:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=2245 Time Limi ...
- 排列-条件求和(Code)
static void Main(string[] args) { // Generate data int arraySize; int[] data; Random rnd; arraySize ...
- Cocos2d-x layout (二)
相对某个控件进行布局 Size widgetSize = Director::getInstance()->getWinSize(); Text* alert = Text::create(&q ...
- [C++]const修饰符
Date: 2014-1-1 Summary: const 修饰符笔记 Contents: 1.const 修饰符 声明一个常量数据类型 , 在编译时就确定数据类型 2.const 与 指针 一般情况 ...
- JEECG开源团队招募新成员 2014年
JEECG开源团队招募新成员 2014年 截止日期:2014-06-01 JEECG开源项目 是一款基于代码生成器的微云高速开发平台.提供企业高速开发和採用微信实现移动应用的解决方式.J ...
- filter与servlet对照
最近在开java物自,还记得刚开始使用servlet这是一个调试ajax什么时候,然后,我不知道怎么用,你知道写的路径来调用,总是提示404错,,到最后自己一点点的调通了,知道servlet是须要se ...
- C++ Primer 学习笔记_61_重载操作符与转换 --自增/自减操作符
重载操作符与转换 --自增/自减操作符 引言: 自增,自减操作符常常由诸如迭代器这种类实现,这种类提供相似于指针的行为来訪问序列中的元素.比如,能够定义一个类,该类指向一个数组并为该数组中的元素提供訪 ...
- poj - 1170 - Shopping Offers(减少国家dp)
意甲冠军:b(0 <= b <= 5)商品的种类,每个人都有一个标签c(1 <= c <= 999),有需要购买若干k(1 <= k <=5),有一个单价p(1 & ...
- Burp Suite抓包、截包和改包
Burp Suite..呵呵.. 听说Burp Suite是能够监測.截取.改动我们訪问web应用的数据包,这么牛X? 条件:本地网络使用代理.由Burp Suite来代理.也就是说,每一个流出外网的 ...