解法
不是解法的生产者,只是官方解法的搬运工= =
官方解法:https://leetcode.com/problems/super-egg-drop/solution/
总共有两大类解法,一种是基于常规DP/递归思路的,把问题不断拆成子问题;另一种是反其道行之,考虑在K个鸡蛋的情况下,t次操作最多能在多少层楼的情况下确定F,找到最小的t即可
基于DP/递归
我们假设给定K,N的时候的解为 T ( K , N ) T(K,N) T(K,N),假设第一次从x楼扔下来,那么显然:
- 如果鸡蛋没碎,F应该在x楼以上,这时候我们还有K个鸡蛋,所以问题变成从
x+1,...,N=x+(N-x)
楼找F的最小步数,显然它为T(K,N-X)
- 如果鸡蛋碎了,那么我们还有K-1个鸡蛋,此时确定F在x以下,问题变成从
1,...,x-1
楼确定F,即T(K-1,x-1)
综上我们得到递归方程式:
T ( K , N ) = 1 + min 1 ≤ x ≤ N { max { T ( K − 1 , x − 1 ) , T ( K , N − x ) } } T(K,N)=1+\min_{1\le x\le N}\{\max\{T(K-1, x-1), T(K, N-x)\}\} T(K,N)=1+1≤x≤Nmin{max{T(K−1,x−1),T(K,N−x)}}
但是直接这么做会超时,所以有两种提高效率的方法,它们都基于一个思想,那就是,当K固定时,T(K,N)
随N递增:
方法一:二分查找
当K和N都固定时,我们令
T
1
(
x
)
=
T
(
K
−
1
,
x
−
1
)
T_1(x)=T(K-1, x-1)
T1(x)=T(K−1,x−1),
T
2
(
x
)
=
T
(
K
,
N
−
x
)
T_2(x)=T(K, N-x)
T2(x)=T(K,N−x),那么:
T
(
K
,
N
)
=
1
+
min
1
≤
x
≤
N
{
max
{
T
1
(
x
)
,
T
2
(
x
)
}
}
T(K,N)=1+\min_{1\le x\le N}\{\max\{T_1(x), T_2(x)\}\}
T(K,N)=1+1≤x≤Nmin{max{T1(x),T2(x)}}
可以看出,T1随x递增,而T2随x递减。所以
max
1
≤
x
≤
N
{
T
1
(
x
)
,
T
2
(
x
)
}
\max_{1\le x\le N}\{T_1(x), T_2(x)\}
max1≤x≤N{T1(x),T2(x)}就是蓝色部分,所以可以通过二分查找找到最小值所在的地方。
注意: 因为交点的位置可能不是整数,所以最小值在交点两边最近的两个整数点处产生,这时候要比较这两个点,所以二分查找的区间要大于2个点。
class Solution(object):
def superEggDrop(self, K, N):
"""
:type K: int
:type N: int
:rtype: int
"""
dp = {}
def get(k,n):
if (k,n) not in dp:
if k==1:
return n
elif n==0:
return 0
else:
l = 1
r = n
while r - l > 1:
mid = (l + r) >> 1
t1 = get(k-1, mid-1)
t2 = get(k, n-mid)
if t1>t2:
r = mid
elif t2>t1:
l = mid
else:
l = r = mid
dp[(k,n)] = 1+min(max(get(k-1,l-1), get(k,n-l)),max(get(k-1,r-1), get(k,n-r)))
return dp[(k,n)]
return get(K,N)
方法二:动态规划
上面我们得到了:
T
(
K
,
N
)
=
1
+
min
1
≤
x
≤
N
{
max
{
T
(
K
−
1
,
x
−
1
)
,
T
(
K
,
N
−
x
)
}
}
=
1
+
min
1
≤
x
≤
N
{
max
{
T
1
(
x
)
,
T
2
(
x
)
}
}
\begin{matrix} T(K,N)&=1+\min_{1\le x\le N}\{\max\{T(K-1, x-1), T(K, N-x)\}\}\\ &=1+\min_{1\le x\le N}\{\max\{T_1(x), T_2(x)\}\} \end{matrix}
T(K,N)=1+min1≤x≤N{max{T(K−1,x−1),T(K,N−x)}}=1+min1≤x≤N{max{T1(x),T2(x)}}
主要是以x为自变量,当我们以N为自变量的时候发现,只有T2会随N的增大而递增,如图所示,可以看出最优值的交点越来越大了,所以可以用DP来解法,先求出小的N的值,然后再通过它去求大的N的值。
注意T1不变是在K不变的情况下!
所以,当K固定时,我们已经求出了T(K,N-1)
,使得T(K,N-1)
最优的x为x'
,那么我们有:
T
(
K
,
N
)
=
1
+
min
x
′
≤
x
≤
N
{
max
{
T
(
K
−
1
,
x
−
1
)
,
T
(
K
,
N
−
x
)
}
}
T(K,N)=1+\min_{x'\le x\le N}\{\max\{T(K-1, x-1), T(K, N-x)\}\}
T(K,N)=1+x′≤x≤Nmin{max{T(K−1,x−1),T(K,N−x)}}
事实上,假如我们令
T
N
(
x
)
=
max
{
T
(
K
−
1
,
x
−
1
)
,
T
(
K
,
N
−
x
)
}
T_N(x) = \max\{T(K-1, x-1), T(K, N-x)\}
TN(x)=max{T(K−1,x−1),T(K,N−x)},TN是个随着x先递减后递增的函数,它的最小值一定在比x'
大的第一个使得
T
N
(
x
)
<
T
N
(
x
+
1
)
T_N(x)<T_N(x+1)
TN(x)<TN(x+1)的地方,更大的x就不用遍历了
class Solution(object):
def superEggDrop(self, K, N):
"""
:type K: int
:type N: int
:rtype: int
"""
dp = range(N+1) # dp[n] = T(1,n)
for k in xrange(2, K+1):
dp_new = [0] # dp_new[n] = T(k,n), dp[n] = T(k-1, n)
x = 1
for n in xrange(1, N+1):
# dp[x-1] = T(k-1, x-1) = T1(x)
# dp_new[n-x] = T(k, n-x) = T2(x)
# max(dp[x-1], dp_new[n-x]) = TN(x)
while x<n and max(dp[x-1], dp_new[n-x])>max(dp[x],dp_new[n-x-1]):
x += 1
dp_new.append(1+max(dp[x-1], dp_new[n-x]))
dp = dp_new
return dp[-1]
反其道而行之
这个真的强无敌= =
我们不枚举楼的高度,我们枚举操作的次数,并想知道在t次操作,K个鸡蛋的情况下,能确定F的最大楼层高度是多少,假设它为
f
(
t
,
K
)
f(t,K)
f(t,K),很明显
f
(
t
,
K
)
f(t,K)
f(t,K)随t的增加而增大,因为操作次数越多,能确定的楼层数肯定越多,所以我们需要找到最小的一个t'
使得,
f
(
t
′
,
K
)
>
=
N
f(t',K)>=N
f(t′,K)>=N。
然后我们要得到f(t,k)
的递推关系式,假设有n层楼:
- 当在楼层x做了一次操作之后,假如鸡蛋碎了,说明这次操作的楼层较高,我们需要在x-1层楼里通过t-1次操作和k-1个鸡蛋来确定F,所以有: f ( t − 1 , k − 1 ) ≥ x − 1 f(t-1,k-1)\ge x-1 f(t−1,k−1)≥x−1。
- 当在楼层x做了一次操作之后,假如鸡蛋没碎,说明这次操作的楼层较低,我们需要在n-x层楼里通过t-1次操作和k个鸡蛋来确定F,所以有:
f
(
t
−
1
,
k
)
≥
n
−
x
f(t-1,k)\ge n-x
f(t−1,k)≥n−x。
将两个条件加起来我们可以消去x,得到: f ( t − 1 , k − 1 ) + f ( t − 1 , k ) ≥ n − 1 f(t-1,k-1)+f(t-1,k)\ge n-1 f(t−1,k−1)+f(t−1,k)≥n−1,也即: 1 + f ( t − 1 , k − 1 ) + f ( t − 1 , k ) ≥ n 1+f(t-1,k-1)+f(t-1,k)\ge n 1+f(t−1,k−1)+f(t−1,k)≥n
而 f ( t , k ) f(t,k) f(t,k)是n的最大值,所以有:
f ( t , k ) = 1 + f ( t − 1 , k ) + f ( t − 1 , k − 1 ) f(t,k) = 1 + f(t-1, k)+f(t-1, k-1) f(t,k)=1+f(t−1,k)+f(t−1,k−1)
边界值为:
f ( 1 , k ) = 1 , f ( t , 1 ) = t , t ≥ 1 , k ≥ 1 f(1,k)=1,f(t,1)=t, t\ge 1, k\ge 1 f(1,k)=1,f(t,1)=t,t≥1,k≥1
得到递推表达式之后,我们就有:
f ( t , k ) = 1 + f ( t − 1 , k ) + f ( t − 1 , k − 1 ) f ( t , k − 1 ) = 1 + f ( t − 1 , k − 1 ) + f ( t − 1 , k − 2 ) \begin{matrix} f(t,k) = 1 + f(t-1, k)+f(t-1, k-1)\\ f(t,k-1) = 1 + f(t-1, k-1)+f(t-1, k-2) \end{matrix} f(t,k)=1+f(t−1,k)+f(t−1,k−1)f(t,k−1)=1+f(t−1,k−1)+f(t−1,k−2)
将上式相减,得到:
f
(
t
,
k
)
−
f
(
t
,
k
−
1
)
=
[
f
(
t
−
1
,
k
)
−
f
(
t
−
1
,
k
−
1
)
]
+
[
f
(
t
−
1
,
k
−
1
)
−
f
(
t
−
1
,
k
−
1
)
]
f(t,k)-f(t,k-1) = [f(t-1,k)-f(t-1,k-1)]+[f(t-1,k-1)-f(t-1,k-1)]
f(t,k)−f(t,k−1)=[f(t−1,k)−f(t−1,k−1)]+[f(t−1,k−1)−f(t−1,k−1)]
令
g
(
t
,
k
)
=
f
(
t
,
k
)
−
f
(
t
,
k
−
1
)
,
t
≥
1
,
k
≥
2
g(t,k) = f(t,k)-f(t,k-1), t\ge 1,k\ge 2
g(t,k)=f(t,k)−f(t,k−1),t≥1,k≥2,边界值为:
g
(
1
,
k
)
=
0
,
g
(
t
,
2
)
=
(
t
−
1
)
t
2
g(1,k) = 0, g(t,2) = \frac{(t-1)t}{2}
g(1,k)=0,g(t,2)=2(t−1)t
则有:
g
(
t
,
k
)
=
g
(
t
−
1
,
k
)
+
g
(
t
−
1
,
k
−
1
)
,
t
≥
2
,
k
≥
3
g(t,k) = g(t-1,k)+g(t-1,k-1), t\ge 2,k\ge 3
g(t,k)=g(t−1,k)+g(t−1,k−1),t≥2,k≥3
观察一下g(t,k)
的递推表达式,是不是有点像集合的表达式?对照一些边界值我们可以归纳出:
g
(
t
,
k
)
=
C
t
k
g(t,k) = C^{k}_{t}
g(t,k)=Ctk
那么由于
f
(
t
,
k
)
=
f
(
t
,
k
−
1
)
+
C
t
k
f(t,k)=f(t,k-1)+C^{k}_t
f(t,k)=f(t,k−1)+Ctk,最终我们得到:
f
(
t
,
k
)
=
∑
k
′
=
1
k
C
t
k
f(t,k)=\sum_{k'=1}^{k}C^k_t
f(t,k)=k′=1∑kCtk
最后用二分查找找到最近的t即可
class Solution(object):
def superEggDrop(self, K, N):
"""
:type K: int
:type N: int
:rtype: int
"""
l = 1
r = N
while r>l:
mid = (r+l)>>1
f = 1
c = 1
for x in xrange(1,K):
c = c* (mid-x+1)/x
f += c
if f>=N:
r = mid
else:
l = mid+1
return r