egg drop是一道dp经典题
887. Super Egg Drop
You are given k identical eggs and you have access to a building with n floors labeled from 1 to n.
You know that there exists a floor f where 0 <= f <= n such that any egg dropped at a floor higher than f will break, and any egg dropped at or below floor f will not break.
Each move, you may take an unbroken egg and drop it from any floor x (where 1 <= x <= n). If the egg breaks, you can no longer use it. However, if the egg does not break, you may reuse it in future moves.
Return the minimum number of moves that you need to determine with certainty what the value of f is.
Example 1:
Input: k = 1, n = 2
Output: 2
Explanation:
Drop the egg from floor 1. If it breaks, we know that f = 0.
Otherwise, drop the egg from floor 2. If it breaks, we know that f = 1.
If it does not break, then we know f = 2.
Hence, we need at minimum 2 moves to determine with certainty what the value of f is.
Example 2:
Input: k = 2, n = 6
Output: 3
Example 3:
Input: k = 3, n = 14
Output: 4
假如说一个大楼一共有T层,然后第K层是当前要扔的鸡蛋的层数,
有两种可能,第一种是k层鸡蛋碎了,那么说明答案就在0 ~ k-1层之间
第二种可能是鸡蛋没碎,那么说明答案是在k ~ N层之间
如果当前有N个鸡蛋,那么M(K, N) = max(M(K-1, N-1),M(K, T-N)) + 1,M(K-1, N-1)是因为鸡蛋碎了,只剩下N-1个鸡蛋了,而取max是因为egg drop最终要求求的是最多需要操作几次
递推公式写出来之后,就要看如何初始化了,如果只有一个鸡蛋,那么我们只能一个楼层一个楼层的去试,也就是M(1, N) = N;
如果只有一层楼,那么不管我们有多少个鸡蛋,我们也只需要试一次,也就是M(K,1) = 1;
还有一个需要考虑的问题,上面我们已经得出了M(K, N)的递推表达式,K需要我们要从1-N依次去遍历,然后得出最小值,也就是M(T,N) = min(M(1, N), M(2, N), ... , M(K, N), ..., M(T-1, N));
class Solution {
public int superEggDrop(int k, int n) {
int[][] dp = new int[k+1][n+1];
for(int j = 1; j <= n; j++) {
dp[1][j] = j;
}
for(int i = 1; i <= k; i++) {
dp[i][1] = 1;
}
for(int i = 2; i <= k; i++) {
for(int j = 2; j <= n; j++) {
dp[i][j] = Integer.MAX_VALUE;
}
}
for(int i = 2; i <= k; i++) {
for(int j = 2; j <= n; j++) {
for(int t = 1; t < j; t++) {
dp[i][j] = Math.min(Math.max(dp[i-1][t-1], dp[i][j-t]) + 1, dp[i][j]);
}
}
}
return dp[k][n];
}
}
但是上面解法的问题是时间复杂度过高,O(KN^2), 所以我们可以采取二分法来优化时间复杂度
当N不变时,M(K-1, N-1)是一个随着K单调递增的函数,M(K, T-N)是一个随着K单调递减的函数,所以两个函数最终会有交点,但是这个交点不一定是整数。如果非整数交点,则需要比较交点横坐标左右两侧最近的两个值,然后取最小值,这是为什么在二分查找的过程中需要记录res的原因,这样我们就可以通过logN的时间复杂度去取到M(N),最终的时间复杂度是O(KNlogN)
class Solution {
public int superEggDrop(int k, int n) {
int[][] dp = new int[k+1][n+1];
for(int j = 1; j <= n; j++) {
dp[1][j] = j;
}
for(int i = 1; i <= k; i++) {
dp[i][1] = 1;
}
for(int i = 2; i <= k; i++) {
for(int j = 2; j <= n; j++) {
dp[i][j] = Integer.MAX_VALUE;
}
}
for(int i = 2; i <= k; i++) {
for(int j = 2; j <= n; j++) {
int start = 1;
int end = j;
int res = Integer.MAX_VALUE;
while(start <= end) {
int mid = start + (end - start) / 2;
// dp[i-1][mid-1]单调递增 dp[i][j-mid]单调递减
if(dp[i-1][mid-1] == dp[i][j-mid]) {
res = Math.min(res, dp[i-1][mid-1]+1);
break;
} else if (dp[i-1][mid-1] > dp[i][j-mid]) {
end = mid - 1;
res = Math.min(res, dp[i-1][mid-1]+1);
} else {
start = mid + 1;
res = Math.min(res, dp[i][j-mid]+1);
}
}
dp[i][j] = res;
}
}
return dp[k][n];
}
}