前段时间去一家厂面试芯片验证专家,这家厂的母厂是做互联网的。一面拿下,二面前半截也挺顺利,直到面试官问了这么一道题:
有一栋楼共100层,一个鸡蛋从第X层及以上的楼层落下来会摔破, 在第X层以下的楼层落下不会摔破。给你2个鸡蛋,求最少要扔多少次鸡蛋,才能保证在最坏的情况下都能找到X层。
听完题目我就感觉凉凉了,我知道这是一道经典的算法题,而且感觉似曾相识。我试着给了一些思路,比如二分法之类的(先从50楼扔),但都不是正确答案。而且面试官觉得我思考问题不严谨,缺乏逻辑。
后来我面向搜索引擎研究了一下,懂得了什么是动态规划。这道题最好的解法是运用动态规划。这道题可以扩展为一共M层楼,N个鸡蛋,求最少要扔多少次鸡蛋找到临界层在哪里。
我们先用迭代的思路,即不断地将大问题分解小问题的思路来分析分析。
我们定义F(N,M)为所求的最小尝试此时,N为鸡蛋数,M为楼层数。
假设第一次从第k层往下扔, 有两种结果,碎了,没碎。
1)如果碎了,则X肯定在k楼以下,即1~k-1之间,并且还剩N-1个鸡蛋。这时就形成了子问题,有N-1个鸡蛋,k-1层楼,求最小的尝试次数,即F(N-1, k-1)
2) 如果没碎,则X肯定在k楼以上,总共是M-k层楼。这时就形成了子问题,有N个鸡蛋,M-k层楼,请最小的尝试次数,即F(N, M-k)
因此,原始问题可以转化为,求
max{F(N-1,k-1), F(N,M-k)} + 1在 1<=k<=M上的最小值。
问题到这里,理论上讲已经可以用迭代法进行计算了,但是要进行大量重复的计算,因此时间代价很高,高到M的阶乘级别,高到基本无法计算,我试了下用迭代法计算F(2, 100)直接就死机了。
动态规划的核心,就是先计算规模最小的问题,然后将小问题的结果记录下来,在计算更大规模的问题时,直接查表获取小问题的结果,在小问题结果的基础上计算大问题。
运用动态规划解决问题,需要先找到状态转移方程(可以理解为递推关系)和边界条件。
状态转移方程:
F(N,M) = Min{max[F(N-1, k-1), F(N, M-k)] +1} ,1<=k<=M
边界条件
F(1, M) = M
Talk is cheap, shou you the code. Life is short, I use Python.
import numpy as np
def throw_egg(num_egg, num_floor):
rslt_list=np.zeros((num_egg+1, num_floor+1), dtype=int)
for in in range(1, num_floor+1):
rslt_list[1][i] = i
if (num_egg < 2):
return num_floor
for N in range(2, num_egg+1):
for M in range(1, num_floor+1):
min_num = M
for k in range(1, M+1):
num = max(rslt_list[N-1, k-1], rslt_list[N, M-k]) + 1
if (num < min_num):
min_num = num
rslt_list[N][M] = min_num
return rslt_list[egg_num][floor_num]