一道有趣的面试题

日前在网上看到一道面试题。颇有意思,也细细的研究一番。现将该题发布于此,和各位交流一下。

  某幢大楼有100层。你手里有两颗一模一样的玻璃珠。当你拿着玻璃珠在某一层往下扔的时候,一定会有两个结果,玻璃珠碎了或者没碎。这幢大楼有个临界楼层。低于它的楼层,往下扔玻璃珠,玻璃珠不会碎,等于或高于它的楼层,扔下玻璃珠,玻璃珠一定会碎。玻璃珠碎了就不能再扔。现在让你设计一种方式,使得在该方式下,最坏的情况扔的次数比其他任何方式最坏的次数都少。也就是设计一种最有效的方式。

  例如:有这样一种方式,第一次选择在60层扔,若碎了,说明临界点在60层及以下楼层,这时只有一颗珠子,剩下的只能是从第一层,一层一层往上实验,最坏的情况,要实验59次,加上之前的第一次,一共60次。若没碎,则只要从61层往上试即可,最多只要试40次,加上之前一共需41次。两种情况取最多的那种。故这种方式最坏的情况要试60次。

  那该如何设计方式呢?

  仔细分析一下,关键是第一次的选择,假设在第N层,如果第一次扔的时候就碎了,那么第二颗珠子只能是从第1层开始一层层往上试,此时,最坏的情况为N-1次,加上第一次,则一共为N层。那如果不碎呢,第二颗珠子会从N+1层开始试吗?很显然不会,此时大楼还剩100-N层,问题就转化为100-N,2颗珠子,请设计最有效方式。

  哦,等等想到什么?呵呵,我想到递归

  定义一个函数F(N),表示N层楼最有效方式最坏情况的次数。

  通过上面的分析,有

  F(N)=Min(Max(1,1+F(N-1)),Max(2,1+F(N-2)),……,Max(N-1,1+F(1)))

  F(1)=1

  本面试题就是求F(100)

  下面把解法的代码赋予其后,用的是VB2005

  

复制代码
         
         
         
         
1 Dim F( 100 ) As Integer , i As Integer , j As Integer
2 Dim tC As Integer
3 F( 0 ) = 0
4 F( 1 ) = 1
5
6 For i = 2 To 100
7 F(i) = 100
8 For j = i To 1 Step - 1
9 tC = IIf(j > 1 + F(i - j), i, 1 + F(i - j))
10 If tC < F(i) Then F(i) = tC
11 Next
12 Next
13
14 For i = 1 To 100
15
16 Debug.Print(F(i))
17 Next
复制代码

 


比较有意思的    评论

  
#1楼   2009-12-20 21:18 |  HCOONa   
没看明白楼主所谓的“最坏的方式”是什么含义……
这道题什么意思呢,折半查找?
支持(0) 反对(0)
  
#2楼 [ 楼主2009-12-20 21:40 |  万仓一黍   
不会是折半,第一颗珠子碎了以后,第二颗珠子只能是从第一层开始层层往上试了
支持(0) 反对(0)
  
#3楼   2009-12-20 22:18 |  XeonWell   
10
支持(0) 反对(0)
  
#4楼   2009-12-20 22:34 |  woka   
最坏的情况为至少试探49次

第一次从50楼扔

若破了,从第一层开始试探,到第48层停止,因为如果前48层都没碎的话,临界层就一定是49了。往上同理。

两个珠子应该不用递归吧?最好的就是试探49次,其他都大于49次
支持(0) 反对(0)
  
#5楼   2009-12-20 22:53 |  winter-cn   
第一颗珠子没碎还可以继续扔......
支持(0) 反对(0)
  
#6楼   2009-12-20 23:11 |  Jianqiang Bao   
14次是正解。
当初就因为这道题没进去MS。
k-1,k-2,k-3直到0,相加,唉,不写下去了,越吻越伤心
支持(0) 反对(0)
  
#7楼   2009-12-20 23:11 |  Jianqiang Bao   
绝对不是递归,而是一个一元二次方程
支持(0) 反对(0)
  
#8楼   2009-12-20 23:53 |  Cat Chen   
看了一下思路,然后没看你的代码,立即打开Firebug用JavaScript写了一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var  maxTries = [0, 1];
var  testFloor = [[], [1]];
var  f =  function (i) {
   if  (i < 2)  return  i;
   if  (maxTries[i])  return  maxTries[i];
   var  tests = [];
   for  ( var  j = 1; j <= i; j++) {
     tests.push(Math.max(j, f(i - j) + 1));
   }
   maxTries[i] = Math.min.apply( this , tests);
   testFloor[i] = [];
   for  ( var  j = 0; j < i; j++) {
     if  (tests[j] == maxTries[i]) testFloor[i].push(j + 1);
   }
   return  maxTries[i];
};


执行f(100)之后,可以看到最多要试14次,首次尝试从9楼到14楼开始都是可以的。
支持(0) 反对(0)
  
#9楼   2009-12-20 23:55 |  Cat Chen   
@包建强
如何一元二次法?
支持(0) 反对(0)
  
#10楼   2009-12-21 00:23 |  hoodlum1980   
扔1次的话,就是从1层开始扔了。没有技巧可言。
扔两次:所以:
1+2+3+4+5+。。。+ N >= 100;
N=?
支持(0) 反对(0)
  
#11楼   2009-12-21 00:30 |  Jianqiang Bao   
逻辑如下:
假设第1个球在第k层摔:
要么摔碎,那么第2个球还要从第1层到第k-1层摔k-1次。2个球一共摔了k次。
要么摔不碎,那么第1个球要在哪一层摔呢?假设是M层,如果摔碎了,那么第一个球摔了2次,为了保持与上一种情况(摔了k次)相等的可能性,第2个球从k+1层到M层最多摔k-2次,那么k层到M层的距离是k-2。
如果第一个球还没摔碎,那么接下来又要在第N层摔,如果摔碎了,那么那么第一个球摔了3次,为了保持与上一种情况(摔了k次)相等的可能性,第2个球从M+1层到N层最多摔k-3次,那么k层到M层的距离是k-3。
以此类推,直到距离为0(最后一次),把这些距离相加,应该等于总距离100,即(k-1)+(k-2)+(k-3)+……+0 =100。计算得到k=14。

就是说:
第1个球在第14层摔,如果碎了,第2个球最多摔13次(摔到第13层),两个球共计摔14次。
如果没碎,往下数13层,第一个球在第27层摔,如果碎了,那么第2个球从15层开始摔,最多摔12次(摔到第26层),这样第一个球摔了2次,第2个球摔了12次,合计14次。
以此类推,如果很不巧,第一个球在第39层、第50层、第60层、第69层、第77层、第84层、第90层、第95层、第99层都没有摔碎,那么在第100层肯定还要摔最后一次,以确定第100层是否会摔碎。
支持(0) 反对(0)
  
#12楼   2009-12-21 03:10 |  trylife[未注册用户]
刷新一看已经看了2个半小时多鸟,,,
  
#13楼   2009-12-21 07:35 |  徐少侠   
又学到一招
哈哈
这个题的确有点意思
支持(0) 反对(0)
  
#14楼   2009-12-21 09:10 |  egmkang   
动态规划,不知道以前在哪个地方看到过
支持(0) 反对(0)
  
#15楼   2009-12-21 09:12 |  生鱼片   
这种在大学的时候肯定比现在作的快
支持(0) 反对(0)
  
#16楼   2009-12-21 09:18 |  Old   
@包建强
学习了!太有意思了!
支持(0) 反对(0)
  
#17楼   2009-12-21 09:22 |  Old   
我第一反应是:
第一个球:
1,3,5...99
如果当前球在N层摔坏了,那么第二个球就从N-1扔。

哈哈,最坏情况要51次。
支持(0) 反对(0)
  
#18楼   2009-12-21 09:28 |  lilac123[未注册用户]
两个都从最低层扔,知道摔碎,这才是解答,程序员就是要用最简单的方式解决最复杂的问题
  
#19楼   2009-12-21 11:12 |  ExtAspNet追随者   
什么意思啊,,
支持(0) 反对(0)
  
#20楼   2009-12-21 11:29 |  一滴water   
递归方法是正确的,只是效率存在问题,实际上是遍历了两个变量,一个是摔坏的层数,二个是选择在哪层摔的层数。
一元二次方程建模建的好,首先考虑两个问题:
(1)假定要摔的层数(是有规律还是无规律)
(2)假定了最坏次数的情况k
通过建模效率自然更高。
支持(0) 反对(0)
  
#21楼   2009-12-21 12:10 |  midnight   
@Cat Chen
执行f(100)之后,可以看到最多要试14次,首次尝试从9楼到14楼开始都是可以的。
从 14楼开始
14,28,42,56,70,84,98
最坏情况是到98楼碎了,所以是7+14 =21

最好是从10楼开始,最坏情况要19次

int F = 100;

int temp = 0;
Dictionary sum = new Dictionary();
for (int i = 1; i < F; i++)
{
int tager = F / i + i;
if (F % i == 0) tager -= 1;
sum.Add(i, tager);
if (temp == 0) temp = tager;
else if (tager > temp)
{
Console.WriteLine("开始层:{0} / 次数{1}", (i-1).ToString(), temp.ToString());
break;
}
else temp = tager;
}
支持(0) 反对(0)
  
#22楼   2009-12-21 12:12 |  midnight   
PS:俺代码有点乱
支持(0) 反对(0)
  
#23楼   2009-12-21 12:15 |  gxh9731[未注册用户]
没明白啥意思,题表述没清楚
  
#24楼 [ 楼主2009-12-21 12:18 |  万仓一黍   
@midnight
第一次在14楼,第二次并不在28楼,而是14+13=27楼,第三次是14+13+12=39楼。
故14次是正解
支持(0) 反对(0)
  
#25楼   2009-12-21 12:37 |  czjone   
@Old 要是我就会用1,4,7 <100这样的最坏的次数也就是在30多次~~~
支持(0) 反对(0)
  
#26楼   2009-12-21 12:38 |  czjone   
LZ,我还是觉得用1,4,7 <100 最多就用33次~
支持(0) 反对(0)
  
#27楼   2009-12-21 12:58 |  Cat Chen   
在我的程序里把maxTries和testFloor打印出来,大家就能看到明显的规律。maxTries是这样一个数组:
[0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, ...]

显然这是可以用通式算出来的,只要看看f(10)的结果,就能找通式算f(100),不再需要依赖于递归。
支持(0) 反对(0)
  
#28楼 [ 楼主2009-12-21 13:23 |  万仓一黍   
@Cat Chen
这有一个问题。虽然从前几项能看出计算公式来。可是,除非你证明这个公式的正确性,否则是不能用的,在数学上,这个称局部不能代表全局。就像哥德巴赫猜想,人们用计算机已经验证了前N(N是一个很大的数,我没有记住)项正确的,但一直不能宣称这个猜想是这确的。而递归就解决这个问题。
支持(0) 反对(0)
  
#29楼   2009-12-21 13:29 |  Cat Chen   
@万仓一黍
这就容易喇,这么明显的规律,通常一个数学归纳法能够搞掂。
支持(0) 反对(0)
  
#30楼 [ 楼主2009-12-21 14:18 |  万仓一黍   
@Cat Chen
还真不容易,我试过证明,用归纳法没有成功
支持(0) 反对(0)
  
#31楼   2009-12-21 14:45 |  gxh973121   
为什么不用折半法呢,不到10次就能出来
支持(0) 反对(0)
  
#32楼   2009-12-21 15:00 |  漫笔者   
我也觉的折半算法要快一些,类似快速排序吧
支持(0) 反对(0)
  
#33楼 [ 楼主2009-12-21 15:19 |  万仓一黍   
折半是找不出来的
第一颗在50层楼的话,如果摔碎了。第二颗只能是从一楼开始,最坏的情况为49次加上前面一次,一共50次,10次是到不了的
支持(0) 反对(0)
  
#34楼   2009-12-21 16:39 |  hoodlum1980   
彻底无语!
支持(0) 反对(0)
  
#35楼   2009-12-21 17:52 |  szyicol   
这样的题目,一开始没头绪,强。
支持(0) 反对(0)
  
#36楼   2009-12-21 17:53 |  gxh973121   
引用 万仓一黍:
折半是找不出来的
第一颗在50层楼的话,如果摔碎了。第二颗只能是从一楼开始,最坏的情况为49次加上前面一次,一共50次,10次是到不了的
刚理解题目意思,关键是只有从第二颗只能从第一层开扔
支持(0) 反对(0)
  
#37楼   2009-12-22 08:59 |  NICHOLAS.SUN2[未注册用户]
题目是要求你设计一种方式,而好多留言只给出14次,,,你淫了!
  
#38楼   2009-12-22 09:04 |  NICHOLAS.SUN2[未注册用户]
而且题目没说明一定要找出临界层来,那么以下这种方式是不是比任何一种方式都要少呢,100,99 仅二次
  
#39楼   2009-12-22 12:34 |  麻将   
x+(x-1)+(x-2)+(x-3)+....+1=100
这样算可以不?
支持(0) 反对(0)
  
#40楼   2009-12-22 14:14 |  hsj2010   
引用 包建强:
逻辑如下:
假设第1个球在第k层摔:
要么摔碎,那么第2个球还要从第1层到第k-1层摔k-1次。2个球一共摔了k次。
要么摔不碎,那么第1个球要在哪一层摔呢?假设是M层,如果摔碎了,那么第一个球摔了2次,为了保持与上一种情况(摔了k次)相等的可能性,第2个球从k+1层到M层最多摔k-2次,那么k层到M层的距离是k-2。
如果第一个球还没摔碎,那么接下来又要在第N层摔,如果摔碎了,那么那么第一个球摔了3次,为了保持与上一种情况(摔了k次)相等的可能性,第2个球从M+1层到N层最多摔k-3次,那么k层到M层的距离是k-3。
以此类推,直到距离为0(最后一次),把这些距离相加,应该等于总距...


做个假设 第一次 14 破了,可以在第二楼开始仍。如果破了那肯定是第一楼。 如果没破 那就从第2楼一直到第12楼仍,如果都没破那么就是第13楼

共仍了1+11=12次
支持(0) 反对(0)
  
#41楼 [ 楼主2010-01-27 14:53 |  万仓一黍   
@海岸线
其实本题隐含如下信息。
第二颗珠子如果在第二层摔碎。问题就来了。
第一层可能是临界楼层,在第一层摔不碎
第一层可能不是临界楼层,在第一层就摔碎了,临界楼层是0层
支持(0) 反对(0)
  
#42楼   2010-01-27 15:39 |  海岸线   
@万仓一黍
正解,刚才想错了
支持(0) 反对(0)
  
#43楼   2011-03-05 10:44 |  溪云初起   
1
2
3
4
5
6
7
8
9
#include
 
int  fun ( int  x)
{
     int  k = ( int ) sqrt (2.0*x);
     int  up = k*(k+1)/2;
 
     return  x <= up ? k :k+1 ;
}
支持(0) 反对(0)
  
#44楼   2015-05-22 11: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
#include
#include
#include
#include
using  namespace  std;
//f(x)=min(
//  for i=1 to x (
     //max(f(x-i)+1,1+i-2)
     //)
//)    (x>1)
//f(x) = 0 (x=0)
static  int  fval[101];
int  f( int  cengshu){
     if (cengshu==0) return  0;
     if (fval[cengshu]!=0) return  fval[cengshu];
 
     int  ans=INT_MAX;
     for ( int  i=cengshu;i>=1;i--){
         ans=min(ans,max(f(cengshu - i)+1,i-1));
     }
     fval[cengshu]=ans;
     return  ans;
}
/*
  * 14 27 39 50 60 69 77 84 90 95 99
  * 13 12 11 10  9  8  7  6  5  4  3
  *
  */
int  main() {
     int  cengshu=100;
     memset (fval,0, sizeof  fval);
     cout<
 
     return  0;
}


RE: http://www.cnblogs.com/grenet/archive/2009/12/20/1628425.html

评论挺有意思的,所以就一起贴过来了。

你可能感兴趣的:(凌乱)