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

关于临界楼层查找问题最优查找策略,我已经在上一篇博客摔玻璃球(鸡蛋)查找临界楼层中做了详细讲解,本文将继续研究相关问题。

假如题目不要求给出具体的最优查找策略,只问最坏情况下至少需要多少次查找,那么该如何求解呢?LeetCode887题-鸡蛋掉落也提出了相同的问题。

为叙述方便,定义如下变量或函数:玻璃球个数 N N N,玻璃球摔碎个数 N b N_b Nb,查找楼层数 F F F,临界楼层 F c F_c Fc,最大查找次数 T T T N N N T T T确定时可查找的最大楼层数 F M a x F_{Max} FMax F M a x F_{Max} FMax N N N T T T的函数关系 f f f( N N N, T T T),查找路径 P P P

最坏情况下的查找次数即为最大查找次数 T T T,在摔玻璃球(鸡蛋)查找临界楼层中, T T T为二叉查找树的深度,假如构造出二叉查找树即可求出 T T T的值。但如果不构造二叉查找树能不能求 T T T的值呢?

方法一:递推公式法

根据定义, N N N, F F F T T T满足如下关系: f f f( N N N, T T T- 1 1 1)< F F F f f f( N N N, T T T),假如能推导出 f f f( N N N, T T T)的公式,然后将 T T T=1, 2, 3, … 依次代入关系式中,使关系式成立的 T T T即为所求。

按照动态规划思想推导 f f f( N N N, T T T)函数的递推公式

  1. N N N= 1 1 1时,只能从第1层开始向上逐层查找,因此有几次查找机会,最多就能查找几个楼层,即 f f f( 1 1 1, T T T)= T T T
  2. T T T= 1 1 1时,不管有几个玻璃球,只有1次查找机会时,最多只能查找1个楼层,即 f f f( N N N, 1 1 1)= 1 1 1
  3. N N N> 1 1 1, T T T> 1 1 1时,第1个玻璃球在某层查找,若摔碎,则用第2个玻璃球查找该层下方楼层,最多可查 f f f( N N N- 1 1 1, T T T- 1 1 1)个楼层,若没碎,则继续查找该层上方楼层,最多可查 f f f( N N N, T T T- 1 1 1)个楼层,一共最多可查楼层数为下方楼层加上方楼层再加当前所在层: f f f( N N N, T T T)= f f f( N N N- 1 1 1, T T T- 1 1 1)+ f f f( N N N, T T T- 1 1 1)+ 1 1 1。同理可继续拆分 f f f( N N N- 1 1 1, T T T- 1 1 1)和 f f f( N N N, T T T- 1 1 1),直到 N N N= 1 1 1 T T T= 1 1 1,即可求得 f f f( N N N, T T T)的值。

综上, f f f( N N N, T T T)的递推公式为:
    f ( N , T ) = f ( N − 1 , T − 1 ) + f ( N , T − 1 ) + 1 f(N,T)=f(N-1,T-1)+f(N,T-1)+1 f(N,T)=f(N1,T1)+f(N,T1)+1    ( N ≥ 2 , T ≥ 2 ) (N≥2, T≥2) (N2,T2)
初始条件为:
    f ( 1 , T ) = T , f(1,T)=T, f(1,T)=T, f ( N , 1 ) = 1 f(N,1)=1 f(N,1)=1    ( N = 1 (N=1 (N=1 或  T = 1 ) T=1) T=1)

根据 f ( N , T ) f(N,T) f(N,T)递推公式计算可得 N N N, T T T F M a x F_{Max} FMax的数值对照表,如表1所示。已知 N N N F F F,查表可得最大查找次数 T T T

                        表1. N N N, T T T, F M a x F_{Max} FMax数值对照表

N / T N/T N/T 1 2 3 4 5 6 7 8 9 10 11 12 13 14 99 100
1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 99 100
2 1 3 6 10 15 21 28 36 45 55 66 78 91 105 4950 5050
3 1 3 7 14 25 41 63 92 129 175 231 298 377 469
4 1 3 7 15 30 56 98 162 255 385 561 793 1092 1470
5 1 3 7 15 31 62 119 218 381 637 1023 1585 2379 3472
6 1 3 7 15 31 63 126 246 465 847 1485 2509 4095 6475
7 1 3 7 15 31 63 127 254 501 967 1815 3301 5811 9907
8 1 3 7 15 31 63 127 255 510 1012 1980 3796 7098 12910
9 1 3 7 15 31 63 127 255 511 1022 2035 4016 7813 14912

根据表1分别绘制 N N N取不同值(1, 2, 3, 4, 5, + ∞ ∞ )时的 F M a x F_{Max} FMax- T T T函数图像,其中 T T T=1, 2, 3, …, 50,如图1所示。当 N N N=+ ∞ ∞ 时, F M a x F_{Max} FMax= 2 T 2^T 2T- 1 1 1
摔玻璃球(鸡蛋)查找临界楼层(后续)_第1张图片                          图1. F M a x F_{Max} FMax- T T T函数图像

方法二:通项公式法
利用 f f f( N N N, T T T)的递推公式计算 F M a x F_{Max} FMax的值需要递归计算若干子问题的解,这样未免太过繁琐,如果能推导出 f f f( N N N, T T T)的通项公式,然后将 N N N T T T代入公式直接求出 F M a x F_{Max} FMax,就会极大地减少计算量。

f f f( N N N, T T T)的通项公式推导:

推导方法一:

因为 N N N表示玻璃球数量, T T T表示最大查找次数,所以 N N N T T T均为正整数。

  1. N N N 2 2 2, T T T 2 2 2时:
    f ( N , T ) f(N,T) f(N,T)的前 T T T项和为 s ( N , T ) s(N,T) s(N,T),则有
    f ( N , T ) = f ( N − 1 , T − 1 ) + f ( N , T − 1 ) + 1 f(N,T)=f(N-1,T-1)+f(N,T-1)+1 f(N,T)=f(N1,T1)+f(N,T1)+1
          = f ( N − 1 , T − 1 ) + f ( N − 1 , T − 2 ) + f ( N , T − 2 ) + 1 + 1 =f(N-1,T-1)+f(N-1,T-2)+f(N,T-2)+1+1 =f(N1,T1)+f(N1,T2)+f(N,T2)+1+1
          = f ( N − 1 , T − 1 ) + f ( N − 1 , T − 2 ) + f ( N − 1 , T − 3 ) + f ( N , T − 3 ) + 1 + 1 + 1 =f(N-1,T-1)+f(N-1,T-2)+f(N-1,T-3)+f(N,T-3)+1+1+1 =f(N1,T1)+f(N1,T2)+f(N1,T3)+f(N,T3)+1+1+1
          = f ( N − 1 , T − 1 ) + f ( N − 1 , T − 2 ) + f ( N − 1 , T − 3 ) + ⋯ + f ( N − 1 , 1 ) + f ( N , 1 ) + 1 + =f(N-1,T-1)+f(N-1,T-2)+f(N-1,T-3)+⋯+f(N-1,1)+f(N,1)+1+ =f(N1,T1)+f(N1,T2)+f(N1,T3)++f(N1,1)+f(N,1)+1+
           ⋯ + 1 + 1 + 1 ⋯+1+1+1 +1+1+1
          = s ( N − 1 , T − 1 ) + T =s(N-1,T-1)+T =s(N1,T1)+T
  2. N N N= 1 1 1时:
    f ( 1 , T ) = T = T ! 1 ! ( T − 1 ) ! = C T 1 f(1,T)=T=\frac{T!}{1!(T-1)!}=C_T^1 f(1,T)=T=1!(T1)!T!=CT1
    s ( 1 , T ) = 1 2 T ( T + 1 ) s(1,T)=\frac{1}{2}T(T+1) s(1,T)=21T(T+1)
  3. N N N= 2 2 2时:
    f ( 2 , T ) = s ( 1 , T − 1 ) + T = 1 2 ( T − 1 ) T + T = T ! 2 ! ( T − 2 ) ! + T ! 1 ! ( T − 1 ) ! = C T 2 + C T 1 f(2,T)=s(1,T-1)+T=\frac{1}{2}(T-1)T+T=\frac{T!}{2!(T-2)!}+\frac{T!}{1!(T-1)!}=C_T^2+C_T^1 f(2,T)=s(1,T1)+T=21(T1)T+T=2!(T2)!T!+1!(T1)!T!=CT2+CT1
    s ( 2 , T ) = 1 2 × 3 ( T − 1 ) T ( T + 1 ) + 1 2 T ( T + 1 ) s(2,T)=\frac{1}{2×3}(T-1)T(T+1)+\frac{1}{2}T(T+1) s(2,T)=2×31(T1)T(T+1)+21T(T+1)
  4. N N N= 3 3 3时:
    f ( 3 , T ) = s ( 2 , T − 1 ) + T f(3,T)=s(2,T-1)+T f(3,T)=s(2,T1)+T
         = 1 2 × 3 ( T − 2 ) ( T − 1 ) T + 1 2 ( T − 1 ) T + T =\frac{1}{2×3}(T-2)(T-1)T+\frac{1}{2}(T-1)T+T =2×31(T2)(T1)T+21(T1)T+T
         = T ! 3 ! ( T − 3 ) ! + T ! 2 ! ( T − 2 ) ! + T ! 1 ! ( T − 1 ) ! =\frac{T!}{3!(T-3)!}+\frac{T!}{2!(T-2)!}+\frac{T!}{1!(T-1)!} =3!(T3)!T!+2!(T2)!T!+1!(T1)!T!
         = C T 3 + C T 2 + C T 1 =C_T^3+C_T^2+C_T^1 =CT3+CT2+CT1
  5. 归纳可得:
    f ( N , T ) = s ( N − 1 , T − 1 ) + T f(N,T)=s(N-1,T-1)+T f(N,T)=s(N1,T1)+T
          = 1 1 × 2 × . . . × N ( T + 1 − N ) ( T + 2 − N ) . . . T + 1 1 × 2 × . . . × ( N − 1 ) ( T + 2 − N ) =\frac{1}{1×2×...×N}(T+1-N)(T+2-N)...T+\frac{1}{1×2×...×(N-1)}(T+2-N) =1×2×...×N1(T+1N)(T+2N)...T+1×2×...×(N1)1(T+2N)
           ( T + 3 − N ) . . . T + . . . + 1 2 ( T − 1 ) T + T (T+3-N)...T+...+\frac{1}{2}(T-1)T+T (T+3N)...T+...+21(T1)T+T    ( N > 0 , T > 0 ) (N>0, T>0) (N>0,T>0)   (式1)
          = ∑ k = 1 N T ! k ! ( T − k ) ! =\sum_{k=1}^{N}\frac{T!}{k!(T-k)!} =k=1Nk!(Tk)!T!                   ( 0 < N ≤ T ) (0(0<NT)      (式2)
          = ∑ k = 1 N C T k =\sum_{k=1}^{N}C_T^k =k=1NCTk                     ( 0 < N ≤ T ) (0(0<NT)    (式3)
    f ( N , T ) f(N,T) f(N,T)表达式有式1、式2、式3三种形式。式1适用于 N N N> 0 0 0 T T T> 0 0 0的情况,由于式2用到了阶乘,式3用到了组合数,所以式2和式3只适用于 0 0 0< N N N T T T的情况。
    N N N> T T T> 0 0 0时, T T T次查找机会最多只能用到 T T T个玻璃球,因此 f ( N , T ) f(N,T) f(N,T)= f ( T , T ) f(T,T) f(T,T)
    综上, f ( N , T ) f(N,T) f(N,T)通项公式为:
    f ( N , T ) = { ∑ k = 1 N C T k , 0T>0 f(N,T)=\begin{cases}\displaystyle\sum_{k=1}^{N}C_T^k, &\text{0T>0}\end{cases} f(N,T)=k=1NCTk,f(T,T),0N>T>0
    N ≥ T N≥T NT时, f ( N , T ) = ∑ k = 1 T C T k = 2 T − 1 f(N, T)=\sum_{k=1}^{T}C_T^k=2^T-1 f(N,T)=k=1TCTk=2T1

推导方法二:

N N N= 3 3 3, T T T= 4 4 4时, F M a x F_{Max} FMax= f f f( 3 3 3, 4 4 4)= 14 14 14 N b N_b Nb可能取值为1, 2, 3,解空间树如图2所示。根据图2可得 F c F_c Fc取不同值时对应的查找路径 P P P,见表2。表2分析了 F M a x F_{Max} FMax N b N_b Nb的关系,其中查找次序对应玻璃球序号一栏中加下划线的数字表示该玻璃球摔碎。
摔玻璃球(鸡蛋)查找临界楼层(后续)_第2张图片                      图2. 当 N N N=3, F F F=14时的二叉查找树

                        表2. F M a x F_{Max} FMax N b N_b Nb关系分析表

临界楼层 F c F_c Fc
查找路径 P P P
查找次序对应玻璃球序号
1  2  3  4
N b N_b Nb与对应 F c F_c Fc数量 F N b F_{N_b} FNb的关系 组合数含义
7
11
13
14
7  3   5  6
 7 11  9  10
 7 11 13 12
 7 11 13 14
1  2  2  2
1  1  2  2
1  1  1  2
1  1  1  1
N b = 1 N_b=1 Nb=1
F 1 = C 4 1 = 4 F_1=C_4^1=4 F1=C41=4
C 4 1 C_4^1 C41:从4次查找机会中选择有球摔碎的1次,共有4种组合。
3
5
6
9
10
12
7   3   1   2
7   3   5   4
7   3   5   6
7  11  9   8
  7  11  9  10
  7  11   13   12
1  2  3  3
1  2  2  3
1  2  2  2
1  1  2  3
1  1  2  2
1  1  1  2
N b = 2 N_b=2 Nb=2
F 2 = C 4 2 = 6 F_2=C_4^2=6 F2=C42=6
C 4 2 C_4^2 C42:从4次查找机会中选择有球摔碎的2次,共有6种组合。
1
2
4
8
7   3  1    
7   3  1  2 
7   3  5  4 
7  11 9  8 
1  2  3   
1  2  3  3
1  2  2  3
1  1  2  3
N b = 3 N_b=3 Nb=3
F 3 = C 4 3 = 4 F_3=C_4^3=4 F3=C43=4
C 4 3 C_4^3 C43:从4次查找机会中选择有球摔碎的3次,共有4种组合。

由表2可得:
F M a x = F 1 + F 2 + F 3 = C 4 1 + C 4 2 + C 4 3 = 4 + 6 + 4 = 14 F_{Max}=F_1+F_2+F_3=C_4^1+C_4^2+C_4^3=4+6+4=14 FMax=F1+F2+F3=C41+C42+C43=4+6+4=14
推广到一般情况,当 0 0 0< N N N T T T时:
F M a x = f ( N , T ) = ∑ N b = 1 N F N b = ∑ N b = 1 N C T N b F_{Max}=f(N,T)=\sum_{N_b=1}^NF_{N_b}=\sum_{N_b=1}^NC_T^{N_b} FMax=f(N,T)=Nb=1NFNb=Nb=1NCTNb   ( 0 < N ≤ T ) (0(0<NT)
N N N> T T T> 0 0 0时:
F M a x = f ( T , T ) F_{Max}=f(T,T) FMax=f(T,T)   ( N > T > 0 ) (N>T>0) (N>T>0)
方法二所得 f ( N , T ) f(N,T) f(N,T)通项公式与方法一相同。

LeetCode887题-鸡蛋掉落官方题解称其方法三为数学法,但实际上与本文方法一递推公式法相同,利用了动态规划的思想,所以从狭义上来讲该方法还是属于计算机专业的范畴,而本文方法二通项公式法则是利用了数学中的数列的前n项和公式以及组合数公式,因此称其为数学法更为合理贴切。

Python代码实现:

import functools
import math


def max_floors_number_recursive_formula(balls_number, search_times):
    dp = [[0]*(search_times+1) for _ in range(balls_number+1)]
    for i in range(1, search_times+1):
        dp[1][i] = i
    for i in range(1, balls_number+1):
        dp[i][1] = 1
    for i in range(2, balls_number+1):
        for j in range(2, search_times+1):
            dp[i][j] = dp[i-1][j-1] + dp[i][j-1] + 1
    return dp[balls_number][search_times]


def combinations(n, m):
    if n == m:
        return 1
    if m > n//2:
        m = n - m
    return functools.reduce(lambda x, y: x*y, range(n-m+1, n+1)) / functools.reduce(lambda x, y: x*y, range(1, m+1))


def max_floors_number_general_formula(balls_number, search_times):
    if balls_number <= search_times:
        max_floors_number = 0
        for k in range(1, balls_number+1):
            max_floors_number += combinations(search_times, k)
        return max_floors_number
    return max_floors_number_general_formula(search_times, search_times)


def maximum_search_times(balls_number, floors_number):
    max_floors_number = 0
    search_times = math.ceil(math.log2(floors_number+1)) - 1
    while max_floors_number < floors_number:
        search_times += 1
        max_floors_number = max_floors_number_general_formula(balls_number, search_times)
    return search_times


print(maximum_search_times(1, 100))
print(maximum_search_times(2, 100))
print(maximum_search_times(3, 100))
print(maximum_search_times(4, 100))
print(maximum_search_times(5, 100))

实验结果:
100
14
9
8
7

代码解读:

函数max_floors_number_recursive_formula采用递推公式法,通过非递归方式实现。因为递归方式会重复计算子问题导致时间复杂度较高,而非递归方式通过存储子问题的解,避免了重复计算,因此时间复杂度较低。

函数max_floors_number_general_formula采用通项公式法

函数maximum_search_times用来求解最大查找次数 T T T。基本思路如下: N N N为已知数,令 T T T依次取1, 2, 3, …,然后将 N N N, T T T代入 f f f( N N N, T T T)的公式中,使不等式 f f f( N N N, T T T- 1 1 1)< F F F f f f( N N N, T T T)成立的 T T T即为所求。

接下来对基本思路进行优化 T T T一定要从1开始取值吗?由图1可知,当 F F F一定时, N N N越大, T T T越小。当 N N N无限时, T T T值最小,解不等式 f f f( N N N, T T T)= 2 T 2^T 2T- 1 1 1 F F F可得 T T T l o g 2 log_2 log2( F F F+ 1 1 1),因为 T T T是整数,所以 T M i n T_{Min} TMin= ⌈ l o g 2 ⌈log_2 log2( F F F+ 1 1 1) ⌉ ⌉ 。而当 N N N有限时, T T T T M i n T_{Min} TMin,因此 T T T T M i n T_{Min} TMin开始取值即可。

采用递推公式法并对 T T T的取值进行优化的代码提交到LeetCode上,执行用时20ms,在所有Python提交中击败了97.89%的用户,内存消耗12.7MB,在所有Python提交中击败了100.00%的用户。

采用通项公式法并对 T T T的取值进行优化的代码提交到LeetCode上,执行用时16ms,在所有Python提交中击败了99.30%的用户,内存消耗12.9MB,在所有Python提交中击败了86.67%的用户。

参考文献:
[1] lzshlzsh. [百度面试题]100层楼,球可能会在某一层楼摔坏,问用2个球,最坏情况下几次测试可以找出该楼层. (2010)
https://blog.csdn.net/lzshlzsh/article/details/5951447

你可能感兴趣的:(摔玻璃球(鸡蛋)查找临界楼层(后续))