题目:
给定两个强度一样小球(自由落体摔碎需要的最小楼层数相同), 现有一个层高100的大楼, 可以选择从任意楼层抛小球, 小球一旦摔碎, 就无法继续用于测试, 测出小球摔碎所需最低楼层的最优策略是什么呢? 这里最优策略是指最差情况下, 抛小球的次数最小化.
显然最笨的办法有两种:
- 用一个小球, 从1楼到100楼实验. 最差情况下, 需要100层.
- 第一个小球选择从地50层抛下, 最差情况下, 需要51次.
一般大家会想到, 选择层号为10的整数倍的楼层, 最差情况下, 19次.
显然这不是最优结果.
只少还有两种选择楼层的方法:
方法1最差需要17次.
# -*- coding: utf-8 -*
from math import ceil,sqrt
cache={}
# 第一个小球, 等差插值结果不是最优.
# 第1个测试位置是10
# 第2个测试位置是90的平方根, 约等于10
# 第三个: 80的平方根, 9
# ...
def f(n, k):
key="%d_%d" % (n, k)
if key in cache:
return cache[key];
if k==0:
cache[key]=n
return cache[key]
if k==1:
cache[key]=ceil(sqrt(n))
return cache[key]
else:
cache[key]=ceil(sqrt(n-sum([f(n,i) for i in range(1,k)])))
return cache[key]
xs=[f (100, k) for k in range(1,30)]
xs=filter(lambda x:x>0, xs)
layers=map(lambda i:sum(xs[0:i]), range(1, len(xs)+1))
print layers
i_xs=zip(range(1,len(xs)+1), xs)
print i_xs
ns=[a+b for (a,b) in i_xs]
print ns
# 最差情况下, 线上扫描, 不必测试本轮的最后一个楼层, 可以省一个小球.
print "max=%d" % (max(ns)-1)
可以选择16个楼层:
10, 20, 29, 38, 46, 54, 61, 68, 74, 80, 85, 89, 93, 96, 98, 100
而另外一种方法, 目前笔者得到*最优的策略是14*
方法如下:
from math import ceil
ks=range(0,20)
xs=[ (k, ceil((100.0-k*(k+1)/2)/(k+1))) for k in ks]
xs=filter(lambda x: x[1]>0, xs)
xs=map(lambda x:(x[0], x[0]+x[1]),xs)
print xs
可以选择从其中12个楼层抛小球
14, 27, 39, 50, 60, 69, 77, 84, 90, 95, 99, 100
显然第一个小球在前11个选出的楼层的某个楼层摔碎的话, 最差情况下都需要14次. 在最后一个选出的楼层(100层)抛小球, 只需要12层.
有没有更好的算法呢?
从计算复杂性理论或者信息熵角度, 能否估算出最优算法的最差时间复杂度呢? 能否严格证明14是最优解?
===========
@心月狐 在评论中给出了最优结果14的简单优美的证明, 抄录如下:
14次是最少的!
如果我们试验13次,以0代表为未碎1代表碎,可以写出“试验序列”。
如0000000010000,000010001(0000)
将长度不足13的序列后面用0补齐,那么所有可能的实验结果恰好对应长度为13且其中有0~2个1的序列。
所有试验结果的总数是
13C2 + 13C1.+ 13C0
= 78 + 13 + 1
= 92
但总情况数有100(101)种,所以在最坏的情况下是无法确定的。至少要14次才能保证。
==========
完美