一道关于扔球的DP问题

来源:互联网 发布:惠州乐知英语 编辑:程序博客网 时间:2024/05/29 12:57

注:问题的解决感谢广东技术师范学院林智勇老师的指导

(一)问题描述

给定N层楼和i个球。用i个球检测在这N层楼中的某一层t球扔下楼时不碎,而在t+1层球扔下楼时会碎,则t层称为最高安全层。求用i个球一定可以检测N层楼的最高安全层的最少扔球次数。(注:球不碎还可以再用)

Example:

给定i=3个球,检测N=7层楼,最优的情况如下:


由上图可得最少的扔球次数为3。

(二)问题求解思路

下面给定两个思路:

(一)分块思想:举个例子,i=2,N=100

现在设想着你手头只有一个球,那么很显然,你只能从最底层(即第一层)一层一层地往上试,那么考虑最坏情况(即最高安全层在第100层),最大的扔球次数就是100,可见在整个过程中我们只能逐层的尝试,否则在任何一层上球碎了,我们就不能准确的确定最高安全楼层了。(一个球的尝试方法是暴力而且简单的,但对于两个球的情况会有所启示)

我们现在考虑有两个球的情况下,那么我们可以尝试用第一个球来确定一个较小的范围,而第二个球则在这个范围内逐层去尝试(由只一个球的尝试方法得到的启示)。

这时我们很容易的想到将100分成10个10层,第一个球用来确认在哪个10层里,第二个球用来确认具体层数。具体来说就是,拿着第一个球从第10层尝试,只要没碎就再上10层,直至碎了或者爬到楼顶,这样确定了十位数的范围。然后再用第二个球逐层尝试。如第一个球:在第10层扔下没碎,那就在第20层,扔下还是没碎,那就在第30层,扔下还是没碎,继续往上10层,假设在第40层扔下碎了;那么就拿第二个球从31层至39层开始依次逐层尝试,直至排查出最高安全楼层为止。大致思路如下:


这种思法已经比较接近答案了,但你会发现还有问题。比如如果球最高安全楼层为16或者96,用刚刚那个想法的话,这两种情况的总尝试次数并不一样:最高安全楼层为16时,第一个球试了2次就定位了区块;而最高安全楼层为96时,第一个球试了10次才定位了区块。虽然在区块内部的第二个球的逐层尝试是一样的,但96层对应的总尝试次数就多得太多了。原因就是10*10的区块均匀划分对大数不利。

尝试的次数是受到两个因素的制约的:1第一个球选择哪一个分块所用的尝试次数;2第二个球逐层尝试的次数。个因为碎和不碎这两种状态是不对称的,所以第一个球的尝试的过程只能从小数逐渐尝试到大数,而不能反着来。所以均匀划分区块对大数是不公平

明白了这个缺陷,也就知道了改进的基本思想:还是要对100找出一种二维区块划分,但不是均匀划分。对于比较小的楼层部分,其包含的楼层范围可以适当多;越向大数部分走,其包含的楼层范围越来越小。从下往上,每一个区块内所含楼层递减。

对于最高安全楼层比较低的情况,第一个球试的次数少;所以最高安全楼层比较高的情况,则让第二个球试的次数少。用第二个球的尝试次数的减少来弥补第一个球需要尝试的次数的递增,使两个球在不同维度上的尝试次数达到一种微妙的平衡。

按照这个思路,要把上面那个均匀的区块切分改进如下:

 

那么这里的x和n会是多少呢?最后一个区块里包含1个楼层,倒数第二个包含2个楼层,倒数第三个包含3个楼层,继续,各区块包含的楼层是4,5,6,……

于是问题转变为,到包含多少个楼层的时候,100个楼层全部分配完?由于1+2+3+……+13=91,而1+2+3+……+14=105,就是说从1开始累加,加到14时,总和第一次大于100:所以上图里的x是14。

     问题解决:第一个球依次试14,27,39,50,60,69,77,84,90,95,99,100。中间任何一次破碎了,就从上一次的下一层开始用第二个球逐层尝试,直至第二个球也破碎为止。用这个方法,总次数一定不超过14次:当最高安全楼层越来越高时,第一个球试的次数越来越多,但第二个球试的次数越来越少,两者始终维持着一种平衡。

    该思路简单易懂,但当球的的个数(即i的值)增加的时候,分块的会变得越来越难。

本思路参考:

http://mp.weixin.qq.com/s?__biz=MzAxOTc5MDY2NA==&mid=2651974453&idx=1&sn=d94b02b4e36f579320930f5117fd540e&mpshare=1&scene=23&srcid=0426FhZVbioCW1nCHE97kt4X#rd

(二)动态规划思想

下面提供两个方案:

方案一:

一.方案概述

dp(i,j):表示用i个球测试j层楼一定可测出某一层是最高安全层的最少次数,等同于min{ max{ 1+dp(i-1,k-1),1+dp(i,j-k) },0<=k<=j} 。

现在给定i个球,j层楼,假设我们在某一层k扔一个球,那么就会出现两种情况:

(1)如果球碎了,那我们需要往第k层楼以下的楼层去测试。在第k层我们测试了,所以次数要计数1;还需往下的楼层作测试,这时还有i-1个球去测试k-1层楼,即需计次数   dp[i-1][k-1]。综上,在情况(1)下,测试次数为1+dp(i-1,k-1)。

(2)如果球不碎,那我们需要往第k层楼以上的楼层去测试。在第k层我们测试了,所以次数要计数1;还需往上的楼层作测试,这时还有i个球去测试j-k层楼,即需计次数       dp[i][j-k]。综上,在情况(2)下,测试次数为1+dp(i,j-k)。

在(1)(2)两种情况中,我们需要去两者的最大值,因为我们需要保证一定可以测试出最高安全楼层。由上可见,k是一个变量,取值范围为0<=k<=j。这表明我们需要遍历0~j,每次求出12两种情况的最大值,为了简便,我们将0~j遍历所得的最大值记为{m0,m1,m2…mj},我们还需要在这些值(这些值都是可以保证一定能测试出最高安全楼层)中选出最小值,因为我们需要的是最少的次数。综上,dp(i,j)= min{ max{ 1+dp(i-1,k-1),1+dp(i,j-k) },0<=k<=j}。

      这个方案是简单易懂的,但有一定的问题,就是在j足够大的时候。假设给出的j是10^15。那么按照这个方案,求dp(i,j)我需要去循环10^15次(这是不计比较的,若计比较的次数,在求Max的时候就需要比较10^15次,再而求min,又需要10^15-1次比较),这只是求出某一个dp[i][j]的循环次数,若要生成表的话,那循环的次数就是元素个数*10^15次,这将是巨大的了,这是关于该方案时间复杂度的问题,而在该方案的数据存储效果也没有一定的优点,这里还是以j为10^15为假设,那么就需要开一个数组为Array[球的个数][10^15]这样的大小了。所以在此仅提供思考方向,供读者参考。

 

方案二:

一.方案概述

dp(i,j):表示min{ k | 用i个球可尝试的次数为j次一定可测出k层楼的某一层是最高安全层}

现在给定i个球,j次测试次数,假设我们在某一层t扔一个球,那么就会出现两种情况:

(1)所扔球的球碎了,那么t层以上的楼层所扔的球一定会碎,这时我们就需要用剩下的i个球和j-1次测试次数往下去测,即dp(i-1,j-1),那么这时的k= 1+dp(i-1,j-1) +∞=∞(∞表示无穷大)。在这里为k的值作下解释:我们在t层扔球了一个球,t层已经测试了,所以式子出现“+1”,接着t层扔的球碎了,那我们需要往下继续测试,这就对应了式子中的“+dp(i-1,j-1)”,而式子中的“+∞”是t层以上的楼我们是可以确定测试结果的,就相当于我们测试了。综上,在情况1下,k=∞。

(2)所扔球的球不碎,那么这时我们就需要用剩下的i个球和j-1次测试次数往上去测,即dp(i,j-1),而对于t层以下的楼层呢?那所扔的球一定不会碎,那么这时的k= dp(i-1,j-1)+1+dp(i,j-1)。在这里也为k的值作下解释:我们在t层扔球了一个球,t层已经测试了,所以式子依然要“+1”,接着t层扔的球不碎,那我们需要往上继续测试,这就对应了式子中的“+dp(i,j-1)”,而对于t层以下的楼层是可以确定测试结果的,就相当于我们测试了,即“+ dp(i-1,j-1)”(注意t层以下的楼层是需要被统计的,这点是难点,也容易被遗忘)。综上,在情况2下,k= dp(i-1,j-1)+1+dp(i,j-1)。

综合上面两种情况,dp(i,j)=min{k | 用i个球可尝试的次数为j次一定可测出k层楼的某一层是最高安全层}=min{∞,dp(i-1,j-1)+1+dp(i,j-1)}= dp(i-1,j-1)+1+dp(i,j-1)。

二.实例演示

接下来我们用第一个例子(即i=3,N=7)来进行方案一的推导。为了方便读者,下面提供前面的图解:


我们用上面方案一的状态方程求出来的解(用二维数组存)如下图:


下面是dp(3,3)=7的推导过程。我们先给出该推导过程的图解,读者可以结合该图来理解下面的文字说明。


(1)按照状态方程,dp(3,3)=dp(2,2)+1+dp(3,2),说明我们有3个球,可用的测试次数为3,我们在某一层扔一个球,其实扔球的这一层是第4层,为什么呢?我们在某一层扔球,该层已经测试过了,所以计数为1,接着这一层往下的楼层我们已知测试结果即球不会碎,则我们需要将下面的楼层也计数,即dp(2,2)+1,查上方的数组,我们得到结果dp(2,2)+1=4,那么换句话说,我们第一次扔球的层数就是在第4层。

(2)如果在第4层扔球,球碎了,那往下去测试,即dp(2,2);按照状态方程,dp(2,2)=dp(1,1)+1+dp(2,1),同理我们第二次扔球的楼层是第dp(1,1)+1=2层。在第2层扔球又出现两种情况:a.球碎了,那继续往下去测试,即dp(1,1)=dp(0,0)+1+dp(1,0),第三次扔球的楼层是第1+dp(0,0)=1层;b.球不碎,往上去测试,即dp(2,1)=dp(1,0)+1+dp(2,0),那第三次扔球的楼层就是第2+1+dp(1,0)=3层(由于是往上去测试,所以要加上上一次扔球的层数,在这里是上一次扔球的层数是第2层)

(3)如果在第4层扔球,球不碎,那就往上去测试,即dp(3,2);按照状态方程,dp(3,2)=dp(2,1)+1+dp(3,1),同理我们第二次扔球的楼层是第4+dp(2,1)+1=6层。在第6层扔球又有两种情况:a.球碎了,那继续往下去测试,即dp(2,1)=dp(1,0)+1+dp(2,0),第三次扔球的楼层是第4+1+dp(1,0)=5层;b.球不碎,往上去测试,即dp(3,1)=dp(2,0)+1+dp(3,0),那第三次扔球的楼层就是第6+1+dp(2,0)=7层

由上图可见,扔球的结果成一个树状图,每次我们只能走其中的一条路径,那么一定能准确测出最高安全楼层的最少的扔球次数为3。

三.伪代码

根据方案二的原理,其方程如下:


伪代码如下:

for i to 球的个数forj to Maxj(由于j是我们所要求的,则可依题给定j的最大限度Maxj)                  if(i==0||j==0)//无球或无测试次数                          dp[i][j]= 0                  else                          /***在此可以添加条件来输出需要的值***/  dp[i][j] = dp[i][j - 1] + 1 + dp[i - 1][j - 1]

可见该方案的时间复杂度为O(球的个数*Maxj)。

本人是一个喜欢算法的新手,本博客简要的阐明了对一道DP问题(面试题)的解决思路,若本博客有错误需要修改的或对排版风格有要改进的等等的建议,请留言或发邮件给我。写博客是一个互相学习的过程,期待收到您的建议!记住,不要沮丧,好好学习,天天向上!共勉!


原创粉丝点击