法一 巧解
https://leetcode-cn.com/problems/super-egg-drop/solution/ji-dan-diao-luo-xiang-jie-by-shellbye/
其实我们可以换一个思路来想:“求k个鸡蛋在m步内可以测出多少层”。我们令dp[k][m]表示k个鸡蛋在m步内可以测出的最多的层数,那么当我们在第X层扔鸡蛋的时候,就有两种情况:
- 鸡蛋碎了,我们少了一颗鸡蛋,也用掉了一步,此时测出 N − X + d p [ k − 1 ] [ m − 1 ] N - X + dp[k-1][m-1] N−X+dp[k−1][m−1] 层,X和它上面的N-X层已经通过这次扔鸡蛋确定大于F;
- 鸡蛋没碎,鸡蛋的数量没有变,但是用掉了一步,剩余 X + d p [ k ] [ m − 1 ] X + dp[k][m-1] X+dp[k][m−1] ,X层及其以下已经通过这次扔鸡蛋确定不会大于F;
也就是说,我们每一次扔鸡蛋,不仅仅确定了下一次扔鸡蛋的楼层的方向,也确定了另一半楼层与F的大小关系,所以在下面的关键代码中,使用的不再是max,而是加法(这里是重点)。有人问到为什么是相加,其实这里有一个惯性思维的误区,诸多解法中,往往求max的思路是“两种方式中较大的那一个结果”,其实这里的相加,不是鸡蛋碎了和没碎两种情况的相加,而是“本次扔之后可能测出来的层数 + 本次扔之前已经测出来的层数”。
这里的m是扔的次数
class Solution {
public:
int superEggDrop(int K, int N) {
// m 最多不会超过 N 次(线性扫描)
int dp[K + 1][N + 1];
// base case:
// dp[..][0] = 0
// dp[0][..] = 0
for(int i=0;i<K+1;i++) dp[i][0]=0;
for(int j=0;j<N+1;j++) dp[0][j]=0;
int m = 0;
while (dp[K][m] < N) {
m++;
for (int k = 1; k <= K; k++)
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
}
return m;
}
};
作者:wat-2
链接:https://leetcode-cn.com/problems/super-egg-drop/solution/zhi-xing-yong-shi-0-ms-zai-suo-you-java-ti-jia-121/
/
* 鸡蛋掉落,鹰蛋(Leetcode 887):(经典dp)
* 有 K 个鸡蛋,有 N 层楼,用最少的操作次数 F 检查出鸡蛋的质量。
*
* 思路:
* 本题应该逆向思维,若你有 K 个鸡蛋,你最多操作 F 次,求 N 最大值。
*
* dp[k][f] = dp[k][f-1] + dp[k-1][f-1] + 1;
* 解释:
* 0.dp[k][f]:如果你还剩 k 个蛋,且只能操作 f 次了,所能确定的楼层。
* 1.dp[k][f-1]:蛋没碎,因此该部分决定了所操作楼层的上面所能容纳的楼层最大值
* 2.dp[k-1][f-1]:蛋碎了,因此该部分决定了所操作楼层的下面所能容纳的楼层最大值
* 又因为第 f 次操作结果只和第 f-1 次操作结果相关,因此可以只用一维数组。
*
* 时复:O(K*根号(N))
/
class Solution {
public:
int superEggDrop(int K, int N) {
vector<int> dp(K+1);
int ans=0;
while(dp[K]<N){
for(int i=K;i>0;i--) // 从后往前计算
dp[i]=dp[i]+dp[i-1]+1;
ans++;
}
return ans;
}
};
流程打印
class Solution {
public:
int superEggDrop(int K, int N) {
vector<int> dp(K+1);
int ans=0;
while(dp[K]<N){
for(int i=K;i>0;i--){ // 从后往前计算
cout<<"i="<<i<<" ";
dp[i]=dp[i]+dp[i-1]+1;
cout<<"dp["<<i<<"]="<<dp[i]<<" ";
}
cout<<endl;
ans++;
cout<<"ans="<<ans<<endl;
}
return ans;
}
};
a n s = d p [ 1 ] ans=dp[1] ans=dp[1]
输入
2
100
输出
14
i=2 dp[2]=1 i=1 dp[1]=1
ans=1
i=2 dp[2]=3 i=1 dp[1]=2
ans=2
i=2 dp[2]=6 i=1 dp[1]=3
ans=3
i=2 dp[2]=10 i=1 dp[1]=4
ans=4
i=2 dp[2]=15 i=1 dp[1]=5
ans=5
i=2 dp[2]=21 i=1 dp[1]=6
ans=6
i=2 dp[2]=28 i=1 dp[1]=7
ans=7
i=2 dp[2]=36 i=1 dp[1]=8
ans=8
i=2 dp[2]=45 i=1 dp[1]=9
ans=9
i=2 dp[2]=55 i=1 dp[1]=10
ans=10
i=2 dp[2]=66 i=1 dp[1]=11
ans=11
i=2 dp[2]=78 i=1 dp[1]=12
ans=12
i=2 dp[2]=91 i=1 dp[1]=13
ans=13
i=2 dp[2]=105 i=1 dp[1]=14
ans=14
法二 一般思路
暴力(超时)
class Solution {
public:
int recursive(int K,int N){
if(K==1||N==0||N==1) return N;
int minimum=N;
for(int i=1;i<=N;i++){
int tMin=max(recursive(K-1,i-1),recursive(K,N-i))+1;
minimum=min(minimum,tMin);
}
return minimum;
}
int superEggDrop(int K, int N) {
return recursive(K,N);
}
};
DP table(继续超时)
class Solution {
public:
int superEggDrop(int K, int N) {
int dp[K+1][N+1];
for(int i=1;i<=N;i++){
dp[1][i]=i; // 只有一个鸡蛋
dp[0][i]=0; // 没有鸡蛋
}
for(int i=1;i<=K;i++){
dp[i][0]=0; // 第0层
}
for(int k=2;k<=K;k++){ // 从两个鸡蛋开始
for(int n=1;n<=N;n++){
int tMinDrop=N;
for(int x=1;x<=n;x++){
tMinDrop=min(tMinDrop,1+max(dp[k-1][x-1],dp[k][n-x]));
}
dp[k][n]=tMinDrop;
}
}
return dp[K][N];
}
};
二分法
C++ sol (DP) 详解
这里n是楼层
class Solution {
public:
int superEggDrop(int K, int N) {
vector<vector<int>> dp(K+1, vector<int>(N+1, 0));
return helper(dp, K, N);
}
int helper(vector<vector<int>>& dp, int K, int N) {
if (1 == K) return N;
if (0 == N||N == 1) return N;
if (dp[K][N]!=0) return dp[K][N];
int res = INT_MAX;
int l=0, r=N; // broken 单调递增(i-1: 随i增,单调递增);notBroken 单调递减(N-i: 随i增,单调递减)。因此这里可以用二分来求相交点
while (l <= r) {
int mid = (l+r)/2;
int broken = helper(dp, K-1, mid-1);
int notBroken = helper(dp, K, N-mid);
if (broken > notBroken) {
r=mid-1;
res = min(res, broken+1);
} else {
l=mid+1;
res = min(res, notBroken+1);
}
}
dp[K][N] = res;
return res;
}
};