经典问题,想了一个小时无果,只能看答案。
当然自己还是菜,连递归都没想出来。总结就是看第i层的鸡蛋是否碎了.
现在总共有N层,遍历1-N,那么在第i层扔的话,如果碎了,那就在i层以下去找需要测试的次数,但是鸡蛋要少一个,没碎那么在i层以上去找需要测试的次数,鸡蛋不变。不管结果如何,最终的结果是要包含所有情况,所以要取最大值。但是不管往上还是往下,扔的次数都加一。但是怎么选i?
我们可以从1-N之间选,但是肯定有一个最佳策略,也就是最小值
class Solution {
public:
int superEggDrop(int K, int N) {
if(N<=1) return 1;
if(K==1) return N;
int res=N;
for(int i=1;i<N;++i)
{
res=min(res,max(
superEggDrop(K,N-i),//在第i层没有碎,鸡蛋不变
superEggDrop(K-1,i-1)//在第i层碎了,鸡蛋减一
)+1
);
}
return res;
}
};
明白了这个策略,能写出dp,三个循环,复杂度O(KN^2),但是对于K=4,N=10000的情况还是超时。没天理啊,同样的算法java就能通过
class Solution {
public:
int superEggDrop(int K, int N) {
if(N<=1) return 1;
if(K==1) return N;
vector<int> ep(N+1);
for(int i=0;i<=N;++i) ep[i]=(i<=1)?1:i;
vector<vector<int>> dp(K+1,ep);
for(int i=2;i<=K;++i)//K个鸡蛋这里需要注意的是,i从2开始,一者i=1的情况已经是边界条件了,二者如果i从1开始,会出现鸡蛋K=0的情况,所以会出错,也可以给K=0设置为N,也可解决4
for(int j=1;j<=N;j++)//N层
for(int k=1;k<=j;++k)//遍历N层的前n个
dp[i][j]=min(dp[i][j],max(
dp[i][k-1],//第k层碎了
dp[i-1][j-k]
)+1
);
return dp[K][N];
}
};
用二分法去优化,可以到O(KNlogN)。实际上二分法我还是不大会。没法写的太简练,只好把所有情况都写出来
class Solution {
public:
int superEggDrop(int K, int N) {
if(N<=1) return 1;
if(K==1) return N;
vector<int> ep(N+1);
for(int i=0;i<=N;++i) ep[i]=(i<=1)?1:i;
vector<vector<int>> dp(K+1,ep);
for(int i=2;i<=K;++i)//K个鸡蛋
for(int j=1;j<=N;j++)//N层
{
int low=0,high=j-1;
while(low<high){
int mid=low+(high-low)/2;
if(dp[i-1][mid]==dp[i][j-mid])
low=high=mid;
else if(dp[i-1][mid]<dp[i][j-mid])
low=mid+1;
else if(dp[i-1][mid]>dp[i][j-mid])
high=mid-1;
}
dp[i][j]=min(dp[i][j],min(dp[i-1][low],dp[i][j-low])+1);
}
return dp[K][N];
}
};
反向思维去解的话,实在是太难去想了,试着去解释下。但是确实精妙,时间复杂度居然能打到O(KT)
首先抽象出来三个变量,K个鸡蛋,T次,N层。问题为如果用K个鸡蛋去扔,在最坏情况下扔多少次可以得到鸡蛋不碎的层数,层数可能为1-N;
转换为如果用K个鸡蛋去扔,如果扔T次,那么能够得到鸡蛋不碎的楼层最大是多少?
所以有N=f(K,T)。如何得到N(K,T),我们假设已经知道所有k<K和t<T的结果,那么可以知道M=f(k-1,t-1),L=f(k,t-1)的结果,
现在多出来一个鸡蛋一次机会,所以我们第一次直接在f(k-1,t-1)+1层去扔,有两种情况,如果鸡蛋碎了,我们那么我们完全可以通过剩下的k-1个鸡蛋和t-1次机会得到M层以内的准确答案,如果鸡蛋没碎,我们还有k个鸡蛋还有t-1次机会,而这种情况下,我们最多只能再得到L层以内的结果。所以我们最多只能L+M+1层以内的准确结果。
class Solution {
public:
int calFloor(int k,int t)
{
if(k==1||t==1) return t;
return calFloor(k-1,t-1)+calFloor(k,t-1)+1;
}
int superEggDrop(int K, int N) {
int t=1;
while(calFloor(K,t)<N) t++;
return t;
}
};
缕清这之后,可以用DP去做。这里最好用状态压缩法,如果是不压缩的话,dp数组不好设置,如果设置为dp[K+1][N+1],明显造成了极大的浪费,因为即使N很多,实际上T值可能很小(4,10000)最终只需要13次即可。
class Solution {
public:
int superEggDrop(int K, int N) {
if(K<2||N<2) return N;
int floor[K+1],res=0;
memset(floor,0,sizeof(floor));
while(floor[K]<N){
int pre=floor[0];
for(int i=1;i<=K;++i)
{
int now=floor[i];//这里必须要有两个变量,才能保存dp[k-1][t-1]
floor[i]=pre+floor[i]+1;
pre=now;
}
res++;
}
return res;
}
};