关于临界楼层查找问题的最优查找策略,我已经在上一篇博客摔玻璃球(鸡蛋)查找临界楼层中做了详细讲解,本文将继续研究相关问题。
假如题目不要求给出具体的最优查找策略,只问最坏情况下至少需要多少次查找,那么该如何求解呢?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)函数的递推公式:
综上, 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(N−1,T−1)+f(N,T−1)+1 ( N ≥ 2 , T ≥ 2 ) (N≥2, T≥2) (N≥2,T≥2)
初始条件为:
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. 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均为正整数。
推导方法二:
当 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. 当 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 |
|
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
当 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