You are given K
eggs, and you have access to a building with N
floors from 1
to N
.
Each egg is identical in function, and if an egg breaks, you cannot drop it again.
You know that there exists a floor F
with 0 <= F <= N
such that any egg dropped at a floor higher than F
will break, and any egg dropped at or below floor F
will not break.
Each move, you may take an egg (if you have an unbroken one) and drop it from any floor X
(with 1 <= X <= N
).
Your goal is to know with certainty what the value of F
is.
What is the minimum number of moves that you need to know with certainty what F
is, regardless of the initial value of F
?
Example 1:
Input: K = 1, N = 2 Output: 2 Explanation: Drop the egg from floor 1. If it breaks, we know with certainty that F = 0. Otherwise, drop the egg from floor 2. If it breaks, we know with certainty that F = 1. If it didn't break, then we know with certainty F = 2. Hence, we needed 2 moves in the worst case to know what F is with certainty.
Example 2:
Input: K = 2, N = 6 Output: 3
Example 3:
Input: K = 3, N = 14 Output: 4
--------------------------------------------------------------------
解法一:直接DP
(鸡蛋数k,楼层数n)->在这种情况下最少扔dp[k][n]次。想知道递推关系需要在1..n层中每层x都试一下:如果碎了,需要次数1+dp[k-1][x-1] (x层已碎,所以是x-1不是x,把楼层理解成数组);如果没碎,需要次数1+dp[k][n-x](数组前x层肯定不会碎)。每层试的过程中都要能保证测出结果,所以max(dp[k-1][x-1]+1,dp[k][n-x]+1),然后在所有的x中挑最小的min{max(dp[k-1][x-1]+1,dp[k][n-x]+1)}。这种解法显然要K*N*N的复杂度。在leetcode上会超时
class Solution(object):
def superEggDrop(self, K, N):
dp = [[j for j in range(N+1)] for i in range(K+1)]
for k in range(2, K+1): #bug1: for k in range(1, K+1)
for n in range(1, N+1):
for x in range(1,n+1):
dp[k][n] = min(dp[k][n], 1+max(dp[k-1][x-1],dp[k][n-x]))
return dp[K][N]
s = Solution()
print(s.superEggDrop(2,100))
解法二:把解法一二分
解法二注意解法一中的递推表达式dp[k][n]=min{max(dp[k-1][x-1]+1,dp[k][n-x])}中包含两部分,dp[k-1][x-1]和dp[k][n-x]一个递增一个递减,而且俩递增幅度一样,所以刚好dp[k-1][x-1]和dp[k][n-x]最接近的时候最大,利用这一点二分,但是个人不是很喜欢这种解法,感觉比较tricky,而且递归不是很快,贴一个LeetCode官方的代码:
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
memo = {}
def dp(k, n):
if (k, n) not in memo:
if n == 0:
ans = 0
elif k == 1:
ans = n
else:
lo, hi = 1, n
# keep a gap of 2 X values to manually check later
while lo + 1 < hi:
x = (lo + hi) // 2
t1 = dp(k-1, x-1)
t2 = dp(k, n-x)
if t1 < t2:
lo = x
elif t1 > t2:
hi = x
else:
lo = hi = x
ans = 1 + min(max(dp(k-1, x-1), dp(k, n-x))
for x in (lo, hi))
memo[k, n] = ans
return memo[k, n]
return dp(K, N)
解法三:逆向DP(比较推崇这种解法,简单直接)
(鸡蛋数k,扔x次)->在这种情况下最多能测dp[k][x]楼,由于dp[k][x]随着k和x都递增,所以找到刚好dp[k][x]>=N时候的x就是结果。而这种情况下的递归表达式更加直白,不管在哪层楼扔第x次,如果第x次扔碎了,那么已知dp[k-1][x-1]的就是能测的楼层数,如果第x次扔没有碎,那么dp[k][x-1]+1就是能测的楼层数,dp[k][x-1]和dp[k-1][x-1]都先于dp[k][x]得到,所以K*N的复杂度就够了。以下是能通过的代码:
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
dp = [[j for j in range(N + 1)] for i in range(K + 1)]
if (K == 0):
return 0
elif (K == 1):
return N
for k in range(2, K + 1):
for x in range(1, N + 1):
dp[k][x] = dp[k - 1][x - 1] + 1 + dp[k][x - 1]
if (dp[k][x] >= N):
if (k == K):
return x
break
return 0
解法四:把解法三二分掉
解法三的dp[k][x]递增没有问题,如果直接二分,还得类似解法二的思路加cache,还是有递归,试过实际上并不是很快。比较好的思路是观察dp[k][x] = dp[k - 1][x - 1] + 1 + dp[k][x - 1],有点像杨辉三角的递归公式(这个不熟悉肯定想不起来)。
所以,dp[k-1][x] = dp[k-2][x-1] + 1 + dp[k-1][x - 1]。
同时令f(k,x)=dp[k][x]-dp[k-1][x]
那么f(k,x) = f[k-1][x-1]+f[k][x-1] (刚好是杨辉三角,而且初值一样,如果用f(k,x)=dp[k][x]-dp[k][x-1],会发现初值不对)
最后是解法四的代码:
class Solution(object):
def superEggDrop(self, K, N):
def f(x):
ans = 0
r = 1
for i in range(1, K+1):
r *= x-i+1
r //= i
ans += r
if ans >= N: break
return ans
lo, hi = 1, N
while lo < hi:
mi = (lo + hi) // 2
if f(mi) < N:
lo = mi + 1
else:
hi = mi
return lo
解法五(开脑洞解法,不推荐):假设最少尝试次数为x,那么,第一个鸡蛋必须要从第x层扔下,因为:如果碎了,前面还有x - 1层楼可以尝试,如果没碎,后面还有x-1次机会。如果没碎,第一个鸡蛋,第二次就可以从x +(x - 1)层进行尝试,为什么是加上x - 1,因为,当此时,第一个鸡蛋碎了,第二个鸡蛋还有可以从x+1 到 x + (x - 1) - 1层进行尝试,有x - 2次。如果还没碎,那第一个鸡蛋,第三次从 x + (x - 1) + (x - 2)层尝试。碎或者没碎,都有x - 3次尝试机会,依次类推。那么,x次的最少尝试,可以确定的最高的楼层是多少呢? x + (x - 1) + (x - 2) + … + 1 = x(x+1) / 2 那反过来问,当最高楼层是100层,最少需要多少次呢?x(x+1)/2 >= 100, 得到x>=14,最少要尝试14次。