问题描述:
你将获得
K
个鸡蛋,并可以使用一栋从
1
到
N
共有
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
个鸡蛋的情况。
(2)设dp[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)