LeetCode 887. Super Egg Drop 扔鸡蛋问题四种方法

231 篇文章 0 订阅

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次。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值