你将获得 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]中找。我们发现可以将问题转化为规模更小的子问题,因此不难想到递归来解决。
伪代码:
defsuperEggDrop(K,N): ans = N# 暴力枚举从第 i 层开始扔for i inrange(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。
接下来,我们增加两行递归的终止条件,这道题就完成了。
classSolution:defsuperEggDrop(self,K:int,N:int) ->int:if K ==1:return Nif N ==0or N ==1:return N ans = N# 暴力枚举从第 i 层开始扔for i inrange(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。
classSolution:@lru_cache()defsuperEggDrop(self,K:int,N:int) ->int:if K ==1:return Nif N ==0or N ==1:return N ans = N# 暴力枚举从第 i 层开始扔for i inrange(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 的所有组合? 当然是套两层循环啦!
如上,你将 dp[i][j] 看成 superEggDrop(i, j),是不是和递归是一摸一样?
来看下迭代的代码:
classSolution:defsuperEggDrop(self,K:int,N:int) ->int: dp = [[i for _ inrange(K+1)] for i inrange(N +1)]for i inrange(N +1):for j inrange(1, K +1): dp[i][j] = iif j ==1:continueif i ==1or i ==0:breakfor k inrange(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]
classSolution:defsuperEggDrop(self,K:int,N:int) ->int:@cachedeff(m,k):if k ==0or m ==0:return0returnf(m -1, k -1)+1+f(m -1, k) l, r =1, Nwhile l <= r: mid = (l + r) //2iff(mid, K)>= N: r = mid -1else: l = mid +1return l
下面代码区我们实现不带二分的版本。
代码
代码支持:Python, CPP, Java, JavaSCript
Python:
classSolution:defsuperEggDrop(self,K:int,N:int) ->int: dp = [[0] * (N +1) for _ inrange(K +1)]for m inrange(1, N +1):for k inrange(1, K +1): dp[k][m] = dp[k -1][m -1] +1+ dp[k][m -1]if dp[k][m] >= N:return mreturn N # Fallback, should not reach here
CPP:
#include<vector>#include<functional>classSolution {public:intsuperEggDrop(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:
importjava.util.Arrays;classSolution {publicintsuperEggDrop(int K,int N) {int[][] dp =newint[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} */varsuperEggDrop=functionsuperEggDrop(K, N) {constdp=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; } } }returnN; // Fallback, should not reach here}