先看题目:
你⾯前有⼀栋从 1 到 N 共 N 层的楼,然后给你 K 个鸡蛋 ( K ⾄少为 1)。现在确定这栋楼存在楼层 0 <= F <= N ,在这层楼将鸡 蛋扔下去,鸡蛋恰好没摔碎(⾼于 F 的楼层都会碎,低于 F 的楼层都不 会碎)。现在问你,最坏情况下,你⾄少要扔⼏次鸡蛋,才能确定这个楼层 F 呢?
那么这个题目一个如何理解?
首先假设鸡蛋数目不限,我们姑且认为鸡蛋刚好在7层楼碎:那么最坏的情况下就是我们拿着鸡蛋从一楼到六楼去丢鸡蛋,直到第七楼鸡蛋碎(当然要是你直接第七楼扔碎也行,但是这只是运气问题并不是最坏情况) ,那么这个样子的话我们需要扔7次。我们将一层一层逐层扔鸡蛋的方式称为线性查找方式
那么如何减少扔鸡蛋的方式以达到“最少”?有一种办法是二分法查找:
最好的策略是使⽤⼆分查找思路,我先去第
(1 + 7) / 2 = 4
层扔⼀下:
如果碎了说明
⼩于
4
,我就去第
(1 + 3) / 2 = 2
层试
……
如果没碎说明
⼤于等于
4
,我就去第
(5 + 7) / 2 = 6
层试
……
但是!一旦鸡蛋数目有限制,就无法一味的使用二分法查找了:例如我只有一个鸡蛋,按照二分法查找我应该去第4楼扔下,但是如果鸡蛋碎掉,就导致无鸡蛋可以扔的地步。因此只能线性查找。
那么来看看我们的状态转移方程:
当鸡蛋没碎:dp(K,N - i) 鸡蛋没碎,即往i层以上层楼层去查找
当鸡蛋碎了:dp(K-1,i-1) 鸡蛋碎了,鸡蛋数目减一,往i层以下查找
def dp(K, N):
if N == 0: return 0#如果楼层为0,则不需要操作了
if K == 1: return N#如果鸡蛋只剩一个,则只能逐低至高线性搜索
res = float("INF")#将res设置为正无穷
for i in range(1, N + 1):
res = min(res,
max(dp(K - 1, i - 1), dp(K, N - i)) + 1
)
return res
当然这样的函数时间复杂度过于大,因为存在过多的重叠子问题
这边建立一个demo数组来作为备忘录,消除重叠子问题:
# K个鸡蛋,共测试N层楼
demo = dict()#建立备忘录
def dp(K, N):
if N == 0: return 0#如果楼层为0,则不需要操作了
if K == 1: return N#如果鸡蛋只剩一个,则只能逐低至高线性搜索
res = float("INF")
if (K,N) in demo: return demo[(K,N)]#备忘录查找,如果找到直接访问结果
for i in range(1, N + 1):
res = min(res,
max(dp(K - 1, i - 1), dp(K, N - i)) + 1
)
demo[(K, N)] = res#写入备忘录
return res