你将获得 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
示例 3:
输入:K = 3, N = 14
输出:4
提示:
1 <= K <= 100
1 <= N <= 10000
而每一次选择从第几层楼扔之后,剩下的问题似乎是一个规模变小的同样问题。比如选择从 i 楼扔,如果碎了,我们需要的答案就是 1 + f(k-1, i-1),如果没有碎,需要在找 [i+1, n],这其实等价于在 [1,n-i]中找。我们发现可以将问题转化为规模更小的子问题,因此不难想到递归来解决。
伪代码:
def superEggDrop(K, N):
ans = N
# 暴力枚举从第 i 层开始扔
for i in range(1, N + 1):
ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1))
return ans
如上代码:
self.superEggDrop(K - 1, i - 1) 指的是鸡蛋破碎的情况,我们就只剩下 K - 1 个鸡蛋, 并且 i - 1 个楼层需要 check。
self.superEggDrop(K, N - i) + 1 指的是鸡蛋没有破碎的情况,我们仍然有 K 个鸡蛋, 并且剩下 N - i 个楼层需要 check。
接下来,我们增加两行递归的终止条件,这道题就完成了。
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
if K == 1: return N
if N == 0 or N == 1: return N
ans = N
# 暴力枚举从第 i 层开始扔
for i in range(1, N + 1):
ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1))
return ans
可是如何这就结束的话,这道题也不能是 hard,而且这道题是公认难度较大的 hard 之一,肯定不会被这么轻松解决。
实际上上面的代码会 TLE,我们尝试使用记忆化递归来试一下,看能不能 AC。
class Solution:
@lru_cache()
def superEggDrop(self, K: int, N: int) -> int:
if K == 1: return N
if N == 0 or N == 1: return N
ans = N
# 暴力枚举从第 i 层开始扔
for i in range(1, N + 1):
ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K, N - i) + 1))
return ans
如果说递归是用函数调用来模拟所有情况, 那么动态规划就是用表来模拟。我们知道所有的情况,无非就是 N 和 K 的所有组合,我们怎么去枚举 K 和 N 的所有组合? 当然是套两层循环啦!
(图 4. 递归 vs 迭代)
如上,你将 dp[i][j] 看成 superEggDrop(i, j),是不是和递归是一摸一样?
来看下迭代的代码:
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
dp = [[i for _ in range(K+1)] for i in range(N + 1)]
for i in range(N + 1):
for j in range(1, K + 1):
dp[i][j] = i
if j == 1:
continue
if i == 1 or i == 0:
break
for k in range(1, i + 1):
dp[i][j] = min(dp[i][j], max(dp[k - 1][j-1] + 1, dp[i-k][j] + 1))
return dp[N][K]
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
dp = [[i for i in range(N+1)] for _ in range(K + 1)]
for i in range(1, K + 1):
for j in range(N + 1):
dp[i][j] = j
if i == 1:
break
if j == 1 or j == 0:
continue
for k in range(1, j + 1):
dp[i][j] = min(dp[i][j], max(dp[i - 1][k - 1] + 1, dp[i][j - k] + 1))
return dp[K][N]
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
@cache
def f(m, k):
if k == 0 or m == 0: return 0
return f(m - 1, k - 1) + 1 + f(m - 1, k)
l, r = 1, N
while l <= r:
mid = (l + r) // 2
if f(mid, K) >= N:
r = mid - 1
else:
l = mid + 1
return l
下面代码区我们实现不带二分的版本。
代码
代码支持:Python, CPP, Java, JavaSCript
Python:
class Solution:
def superEggDrop(self, K: int, N: int) -> int:
dp = [[0] * (N + 1) for _ in range(K + 1)]
for m in range(1, N + 1):
for k in range(1, K + 1):
dp[k][m] = dp[k - 1][m - 1] + 1 + dp[k][m - 1]
if dp[k][m] >= N:
return m
return N # Fallback, should not reach here
CPP:
#include <vector>
#include <functional>
class Solution {
public:
int superEggDrop(int K, int N) {
std::vector<std::vector<int>> dp(K + 1, std::vector<int>(N + 1, 0));
for (int m = 1; m <= N; ++m) {
for (int k = 1; k <= K; ++k) {
dp[k][m] = dp[k - 1][m - 1] + 1 + dp[k][m - 1];
if (dp[k][m] >= N) {
return m;
}
}
}
return N; // Fallback, should not reach here
}
};
Java:
import java.util.Arrays;
class Solution {
public int superEggDrop(int K, int N) {
int[][] dp = new int[K + 1][N + 1];
for (int m = 1; m <= N; ++m) {
for (int k = 1; k <= K; ++k) {
dp[k][m] = dp[k - 1][m - 1] + 1 + dp[k][m - 1];
if (dp[k][m] >= N) {
return m;
}
}
}
return N; // Fallback, should not reach here
}
}
JavaSCript:
/**
* @param {number} k
* @param {number} n
* @return {number}
*/
var superEggDrop = function superEggDrop(K, N) {
const dp = Array.from({ length: K + 1 }, () => Array(N + 1).fill(0));
for (let m = 1; m <= N; ++m) {
for (let k = 1; k <= K; ++k) {
dp[k][m] = dp[k - 1][m - 1] + 1 + dp[k][m - 1];
if (dp[k][m] >= N) {
return m;
}
}
}
return N; // Fallback, should not reach here
}