摔玻璃球(鸡蛋)查找临界楼层(再续)

关于临界楼层查找问题,我已经在先前的两篇博客摔玻璃球(鸡蛋)查找临界楼层和摔玻璃球(鸡蛋)查找临界楼层(后续)中讲得很清楚了,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+1XFmin(max(T(N1,X1),T(N,FX)))

               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张图片                        图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张图片摔玻璃球(鸡蛋)查找临界楼层(再续)_第3张图片         图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张图片摔玻璃球(鸡蛋)查找临界楼层(再续)_第5张图片         图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层楼的最大查找次数的最优解

你可能感兴趣的:(算法,动态规划,python)