LeetCode:887. Super Egg Drop - Python

189 篇文章 3 订阅
151 篇文章 2 订阅

问题描述:

887. 鸡蛋掉落

你将获得 K个鸡蛋,并可以使用一栋从 1N 共有 N层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F,满足 0 <= F <= N任何从高于 F 的楼层落下的鸡蛋都会碎,从 F楼层或比它低的楼层落下的鸡蛋都不会破。

每次操作,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层X扔下(满足1 <= X <= N)。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小操作次数是多少?

示例 1:

输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。

示例 2:

输入:K = 2, N = 6
输出:3

提示:

  • 1 <= K <= 100
  • 1 <= N <= 10000

问题分析:

这个问题应该是Google面试题,高楼扔鸡蛋问题的推广,可以先分析一下原问题,100层楼,2个鸡蛋的情况。从鸡蛋的角度来看,很容易想到二分法,刚看到这个问题的时候,想到的就是二分法,最后……没做出来。然后是动态规划方法,从楼层的角度出发,得出正解。

1、动态规划方法:

(1)先考虑原问题:100层楼,2个鸡蛋的情况。
2dp[n]表示从第n层丢下鸡蛋,没有摔碎的最少操作次数。先给出dp方程式为:

dp[n] = min(1 + max(i-1, dp[n-i]))   其中:(i in [1, n])  # dp[n]通过遍历之前的值得到。
dp[1] = 1

   解释:
   假设:第一个鸡蛋从第i层扔下来。那么有两个情况。
   A: 碎了,第二个鸡蛋只能从第 1 层,依次向上试,共有操作i - 1次。
   B: 没碎,两个鸡蛋还都健在,楼上还有 n - i层,此时的问题,就转换成本问题的,子问题了dp[n-i]
   C: 所以 max(i-1, dp[n-i]) 表示两种情况最差的一种,也就是操作次数最多的哪一种。
   D: 1 + max(i-1, dp[n-i]),前面那个 1可以理解为本次操作。
   E: 最后,很显然,我们不知道 最优的 i 层 在哪里对吧?所以通过 枚举 或者 遍历 选出来。也就是上面的状态转换方程了。
3现在看看本题,N层楼,K个鸡蛋的情况。
   A:dp[n][k] 为,n 层楼,k 个鸡蛋找到 F的最少操作次数。
   B: 当第一个鸡蛋从第 i 层丢下,
   C: 碎了,那么现在剩下 k - 1 个鸡蛋,此时说明 F 在楼下(i 层的下面),接下来还要进行操作 dp[i-1][k-1] 次(子问题);
   D: 如果没碎,说明此时的 F 在楼上(i 层的上面),接下来还要操作dp[n-i][k] 次(子问题哦)。
所以得出dp方程

dp[n][k] = min(1 + max(dp[i-1][k-1], dp[n-i][k]))  其中:(i in [1, n])
dp[i][1] = i

做到这里,本以为可以AC了,遗憾的是,这种方式基本是对所有情况的检测,有点暴力,所以TLE(也可能是用Python3的缘故,Python2就可以顺利通过)。
(4)继续学习,换个思维方式,再做一次。
   A:dp[m][k] 为,k 个鸡蛋, m次操作(扔m次),可以判定的最大楼层数。现在的dp 方程如下:

dp[m][k] = dp[m - 1][k] + dp[m - 1][k - 1] + 1

   B: 如果当前鸡蛋 - 碎了, 此时,能判断出的楼层数最少dp[m - 1][k - 1]
   C: 如果当前鸡蛋 - 没碎,此时,能判断出的楼层数最多dp[m - 1][ k ] ,现在是不是还有 k个鸡蛋?而这k 个鸡蛋是不是 至少又可以向上 判断出 dp[m - 1][k - 1] 层,(因为之前已经算过了,只要加上就可以了。) 然后在加上当前这 1 层。所以总体就是上面的方程式了。
—–(不知道,这种理解是否正确或者严谨,大神可以指点哦。
   D: dp[m][k] 类似于组合的数量,并且它以指数方式增加到N, 还有一种理解方法,就是可以联想,上台阶问题(也不太严谨哈)。

再分析一个小栗子,假设只有 2 个鸡蛋,至少 扔鸡蛋 X 次,可以判断出100层楼。

很显然,第一个鸡蛋要从X层扔下去,如果烂了,还可以用另外一个鸡蛋从第一层向上开始扔。
现在,希望至少 扔鸡蛋 X 次,就可以判断出100层楼。那就假设鸡蛋一直没烂,第一次扔完鸡蛋之后,可以得出楼层 X,然后还有X-1次机会,第二扔完又没烂,然后还有X-2次机会,这样一推理,可以得到的楼层数为: X + (X - 1) + (X - 2) + … + 1 = X(X+1) / 2 >=100,解方程,求出X,即可。感觉这个例子可以帮助理解上面那个公式:dp[m][k] = dp[m - 1][k] + dp[m - 1][k - 1] + 1。参加链接[1]

Python3实现:

class Solution:
    def superEggDrop(self, K, N):  # 方法1
        dp = [[0] * (K + 1) for i in range(N + 1)]
        for m in range(1, N + 1):
            for k in range(1, K + 1):
                dp[m][k] = dp[m - 1][k - 1] + dp[m - 1][k] + 1
            if dp[m][K] >= N:
                return m

    def superEggDrop1(self, K, N):  # 方法2 + 空间优化
        dp = [0] * (K + 1)
        m = 0
        while dp[K] < N:
            for k in range(K, 0, -1):
                dp[k] = dp[k - 1] + dp[k] + 1
            m += 1
        return m


if __name__ == '__main__':
    solu = Solution()
    print(solu.superEggDrop(2, 100))

声明: 总结学习,有问题可以批评指正,大神可以略过哦。

参考链接:
[1]:blog.csdn.net/linj_m/article/details/9792821
[2]:leetcode.com/problems/super-egg-drop/discuss/158974/C++JavaPython-2D-and-1D-DP-O(KlogN)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值