涉及知识点:动态规划
你将获得 K
个鸡蛋,并可以使用一栋从 1
到 N
共有 N
层楼的建筑。
每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。
你知道存在楼层 F
,满足 0 <= F <= N
任何从高于 F
的楼层落下的鸡蛋都会碎,从 F
楼层或比它低的楼层落下的鸡蛋都不会破。
每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X
扔下(满足 1 <= X <= N
)。
你的目标是确切地知道 F
的值是多少。
无论 F
的初始值如何,你确定 F
的值的最小移动次数是多少?
提示:
1 <= K <= 100
1 <= N <= 10000
示例 1:
输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。
示例 2:
输入:K = 2, N = 6
输出:3
示例 3:
输入:K = 3, N = 14
输出:4
题解一参考了 Bilibili @李永乐老师官方 的一期视频 双蛋问题,李老师讲解的非常细致,很值得学习。
我下面整理一下主要思路,采用的是 动态规划 的算法,定义 “$K$个鸡蛋,$N$层楼” 的解为 $F(K,N)$, 考虑最小子问题:
再考虑状态转移方程,对于问题 $F(K,N)$,考虑:
考虑到 $F(K,N)$ 是个关于 $N$ 的单调递增函数,也就是说在鸡蛋数 $K$ 固定的情况下,楼层数 $N$ 越多,需要的步数一定不会变少。而在状态转移方程中,$F(K-1,t-1)$ 是关于 $t$ 的单调递增函数,$F(K,N-t)$ 是关于 $t$ 的单调递减函数。我们要找的点是它们的最大值的最小点,如下图的红点(T1代表$F(K-1,t-1)$,T2代表$F(K,N-t)$):
为了找到这个红点,可以采用二分法来找,当 mid
点 $F(K-1,mid-1)>F(K,N-mid)$ 时,向左找,否则向右找。
这个算法,时间复杂度为 $O(KNlogN)$,但还是运行超时了。
本解法 Python
代码如下
class Solution(object):
def superEggDrop(self, K, N):
"""
:type K: int
:type N: int
:rtype: int
"""
res = [[0 for _ in range(K+1)] for _ in range(N+1)]
for i in range(1, K+1):
res[1][i] = 1
for i in range(1, N+1):
res[i][1] = i
for i in range(2, N+1):
for j in range(2, K+1):
left, right = 1, i
while left < right:
mid = (left + right + 1) // 2
n_break = res[mid-1][j-1]
n_unbreak = res[i-mid][j]
if n_break > n_unbreak:
right = mid - 1
else:
left = mid
res[i][j] = max(res[left-1][j-1], res[i-left][j]) + 1
return res[-1][-1]
题解二的方法很不常见,我参考了 官方的题解 的方法三。
本解法 Python
代码如下
class Solution(object):
def superEggDrop(self, K, N):
"""
:type K: int
:type N: int
:rtype: int
"""
dp = [[0] * (K + 1) for _ in range(N + 1)]
for i in range(1, N + 1):
for j in range(1, K + 1):
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] + 1
if dp[i][K] >= N:
return i