Leetcode第887题 鸡蛋掉落C++解法

经典问题,想了一个小时无果,只能看答案。
当然自己还是菜,连递归都没想出来。总结就是看第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;  
    }
};

猜你喜欢

转载自blog.csdn.net/meixingshi/article/details/113881268