关于临界楼层查找问题,我已经在先前的两篇博客摔玻璃球(鸡蛋)查找临界楼层和摔玻璃球(鸡蛋)查找临界楼层(后续)中讲得很清楚了,LeetCode中也有相同题目——鸡蛋掉落,本文将对其官方题解的方法一和方法二进行解读。
首先定义变量和函数,玻璃球(鸡蛋)个数 N N N,查找楼层数 F F F,最大查找次数 T T T,当前查找楼层 X X X,当 N N N和 F F F确定时需要的最大查找次数
T T T( N N N, F F F)。
方法一利用了动态规划思想,状态转移方程如下:
T ( N , F ) = 1 + m i n 1 ≤ X ≤ F ( m a x ( T ( N − 1 , X − 1 ) , T ( N , F − X ) ) ) T(N,F)=1+\underset{1≤X≤F}{min}(max(T(N-1,X-1), T(N, F-X))) T(N,F)=1+1≤X≤Fmin(max(T(N−1,X−1),T(N,F−X)))
T ( 1 , F ) = F T(1,F)=F T(1,F)=F, T ( N , 0 ) = 0 T(N, 0)=0 T(N,0)=0
方法二与方法一思想一致,只不过是利用了决策单调性对方法一进行了优化。
状态转移方程中包含2个函数: T 1 T1 T1( X X X)= T T T( N N N- 1 1 1, X X X- 1 1 1), T 2 T2 T2( X X X)= T T T( N N N, F F F- X X X)。 T 1 T1 T1随着 X X X的增加而单调递增, T 2 T2 T2随着 X X X的增加而单调递减。官方题解中的 T 1 T1 T1, T 2 T2 T2函数图像如图1所示,但是仅凭这个示意图,读者恐怕很难想象实际当中的函数图像应当是什么样子,更无法参照图像去模拟方法一和方法二的解题过程。因此,本文将以 N N N=3, F F F=8为例,可视化 T 1 T1 T1, T 2 T2 T2的实际函数图像,然后结合图像与代码对方法二进行详细解读。
图1. LeetCode- T 1 T1 T1, T 2 T2 T2函数图像
方法一的Python代码实现:
def superEggDrop(N, F):
T = {}
def dp(n, f):
if (n, f) not in T:
if f == 0:
ans = 0
elif n == 1:
ans = f
else:
low, high = 1, f
while low + 1 < high:
x = (low + high) // 2
t1 = dp(n-1, x-1)
t2 = dp(n, f-x)
if t1 < t2:
low = x
elif t1 > t2:
high = x
else:
low = high = x
ans = 1 + min(max(dp(n-1, x-1), dp(n, f-x)) for x in (low, high))
T[n, f] = ans
return T[n, f]
return dp(N, F)
方法二的Python代码实现:
def superEggDrop(N, F):
T = [[0]*(F+1) for _ in range(N+1)]
for i in range(F+1):
T[1][i] = i
for n in range(2, N+1):
x = 1
for f in range(1, F+1):
while x < f and max(T[n-1][x-1], T[n][f-x]) >= max(T[n-1][x], T[n][f-x-1]):
x += 1
T[n][f] = 1 + max(T[n-1][x-1], T[n][f-x])
return T[N][F]
根据方法一或方法二的代码可得 N N N=3, F F F=8时的 T T T( N N N, F F F)函数数值对照表,如表1所示。其中 N N N=1和 F F F=0的情况属于初始条件,函数值无需计算,而 N N N=2或3, F F F>0的情况则需通过状态转移方程对函数值进行求解。
根据方法一的思路及表1可得 T 1 T1 T1和 T 2 T2 T2的函数图像,如图2( N N N=2)和图3( N N N=3)所示。 T 1 T1 T1与 F F F无关,因此其图像为1条折线; T 2 T2 T2与 F F F有关,当 F F F增加时, T 2 T2 T2在每个整数点上都是单调递增的,因此其图像为1组折线。参照图2和图3即可模拟方法一的解题过程。
方法二由于利用了决策单调性,对于 T 2 T2 T2函数的某一条折线,不必计算 X X X的全部取值所对应的函数值,因此其对应的函数图像为方法一对应图像的局部图,如图4( N N N=2)和图5( N N N=3)所示。参照图4和图5即可模拟方法二的解题过程。
表1. T T T( N N N, F F F)函数数值对照表
N \ F | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
1 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
2 | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 4 |
3 | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 4 |
图2. N N N=2时的 T 1 T1 T1, T 2 T2 T2函数图像 图3. N N N=3时的 T 1 T1 T1, T 2 T2 T2函数图像
图4. N N N=2时的 T 1 T1 T1, T 2 T2 T2函数图像(局部) 图5. N N N=3时的 T 1 T1 T1, T 2 T2 T2函数图像(局部)
下面截取方法二代码运行过程中的一个片段,并结合图4中的函数图像对方法二进行解读:
伪代码 | 注释 | |
---|---|---|
1 | N=2, F=3, X=2 | 一共有2个鸡蛋,3层楼,当前查找楼层为第2层 |
2 | condition1: X保证楼层数非负。若condition1成立,则顺序执行;若不成立,则跳转到第11行,计算T(2,3)的值 |
|
3 | T1(2)=T(1,1)=1 | 若鸡蛋在第2层摔碎,则只剩1个鸡蛋,需要查找下方1个楼层,最大查找次数为1 |
4 | T2(2)=T(2,1)=1 | 若鸡蛋在第2层没有摔碎,则仍有2个鸡蛋,需要查找上方1个楼层,最大查找次数为1 |
5 | Tmax1=max(T1(2), T2(2))=1 | 若从第2层开始查找,其上方或下方楼层在最坏情况下的最大查找次数为1 |
6 | T1(3)=T(1,2)=2 | 假设当前查找楼层为第3层。若鸡蛋在第3层摔碎,则只剩1个鸡蛋,需要查找下方2个楼层,最大查找次数为2 |
7 | T2(3)=T(2,0)=0 | 若鸡蛋在第3层没有摔碎,则仍有2个鸡蛋,需要查找上方0个楼层,最大查找次数为0 |
8 | Tmax2=max(T1(3), T2(3))=2 | 若从第3层开始查找,其上方或下方楼层在最坏情况下的最大查找次数为2 |
9 | condition2: Tmax1≥Tmax2 | 判断第3层上下方楼层在最坏情况下的最大查找次数是否大于第2层的情况。 若是即condition2不成立,则跳转到第11行,计算T(2,3)的值;若否即condition2成立,则顺序执行 |
10 | X+=1 | 当前查找楼层更新为第3层,然后跳转到第2行继续执行 |
11 | T(2,3)=1+max(T1(2),T2(2))=1+1=2 | 从第2层开始查找,其上下方楼层在最坏情况下的最大查找次数最小,再加上在第2层的1次查找, 结果即为2个鸡蛋查找3层楼的最大查找次数的最优解 |