经典面试题-扔鸡蛋问题 Leetcode 887. 鸡蛋掉落

很经典的题目,力扣上的链接:887. 鸡蛋掉落

题目大意是:有K个鸡蛋,N层楼,求你找出在最坏情况下最小的尝试次数找出鸡蛋碎的临界楼层。

解析

理解题目建议看看李永乐老师的视频:复工复产找工作?先来看看这道面试题:双蛋问题
如果不限制鸡蛋次数,想找出最少的尝试次数,直接二分区间即可,比如一共8层,第一次看看4层碎不碎,4层不碎,去6层扔…,4层碎了,去2层扔看看碎不碎…
何为最坏情况:鸡蛋破碎一定发生在搜索区间穷尽时,同时也要保证在该种情况下使得尝试次数最少。

现在给了鸡蛋的限制,只有K个,这个时候就不能直接用二分了。比如说只给你 1 个鸡蛋,7 层楼,你敢用二分吗?你直接去第 4 层扔一下,如果鸡蛋没碎还好,但如果碎了你就没有鸡蛋继续测试了,无法确定鸡蛋恰好摔不碎的楼层 F 了。这种情况下只能用线性扫描的方法,算法返回结果应该是 7 [ 1 ] ^{[1]} [1]

其实这样的话,如果使用三分,五分,十分都可以尽可能的减少尝试次数,无法直接找出最优策略,我们可以使用计算进行求解,也就是暴力枚举,在任意一个区间 [ i , j ] [i,j] [i,j],分别尝试从k层扔下鸡蛋,其中 k ∈ [ i , j ] k\in[i,j] k[i,j],慢慢递推,也就是动态规划的思想。

d p [ K ] [ N ] dp[K][N] dp[K][N]表示 K K K个鸡蛋, N N N层楼,找出最坏情况下最少的尝试次数的大小,假设在第i层扔鸡蛋:

  • 如果碎了,此时剩下 K − 1 K-1 K1个鸡蛋,临界就要从下面的 i − 1 i-1 i1层楼里面找,问题就变成了 d p [ K − 1 ] [ i − 1 ] dp[K-1][i-1] dp[K1][i1]
  • 如果没碎,还是有 K K K个鸡蛋,临界就要从上面的 N − i N-i Ni层楼里面找,问题就变成了 d p [ K ] [ N − i ] dp[K][N-i] dp[K][Ni]

无论碎没碎,扔这个鸡蛋我们都尝试了一次,所以最后要加1。
d p [ K ] [ N ] = min ⁡ i ∈ [ 1 , N ] { max ⁡ ( d p [ K − 1 ] [ i − 1 ] , d p [ K ] [ N − i ] ) + 1 } dp[K][N] = \min_{i\in[1,N]}\{ \max(dp[K-1][i-1],dp[K][N-i])+1\} dp[K][N]=i[1,N]min{max(dp[K1][i1],dp[K][Ni])+1}

无优化

class Solution {
public:
    int superEggDrop(int K, int N) {
        int dp[K+1][N+1];
        memset(dp,0,sizeof(dp));
        // 初始化
        for(int i=1;i<=K;i++) dp[i][1] = 1;
        for(int i=1;i<=N;i++) dp[1][i] = i;

        for(int i=2;i<=K;i++){
            for(int j=2;j<=N;j++){
                dp[i][j] = 100000000;
                for(int k=1;k<=j;k++){
                    dp[i][j] = min(dp[i][j],max(dp[i-1][k-1],dp[i][j-k])+1);
                }
            }
        }

        return dp[K][N];     
    }
};

时间复杂度: O ( K N 2 ) O(KN^2) O(KN2) 空间复杂度: O ( K N ) O(KN) O(KN)

二分优化

class Solution {
public:
    int superEggDrop(int K, int N) {
        int dp[K+1][N+1];
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=K;i++) dp[i][1] = 1; // i个鸡蛋,1层楼高,最坏一次
        for(int i=1;i<=N;i++) dp[1][i] = i; // 1个鸡蛋,i层楼高,最坏i次

        for(int i=2;i<=K;i++){
            for(int j=2;j<=N;j++){
                int left = 1, right = j,up,down;
                dp[i][j] = 100000000;
                while(left<=right){
                    int mid = (left+right)/2;
                    up = dp[i][j-mid];   // 第mid层,扔鸡蛋没碎
                    down = dp[i-1][mid-1];  // 第mid层,扔鸡蛋碎了,剩下i-1个鸡蛋
                    if(up>down) {
                        dp[i][j] = min(dp[i][j],up+1); left = mid +1;
                    }
                    else if(up<down) {
                        dp[i][j] = min(dp[i][j],down+1); right  = mid-1;
                    }
                    else {
                        dp[i][j] = min(dp[i][j],up+1);  break;
                    }
                }
            }
        }
        return dp[K][N];
    }
};

时间复杂度: O ( K N l o g N ) O(KNlogN) O(KNlogN) 空间复杂度: O ( K N ) O(KN) O(KN)

逆向思维再优化 [ 2 ] ^{[2]} [2]

本题应该逆向思维,若你有 K 个鸡蛋,你最多操作 F 次,求 N 最大值。
dp数组换一种表示含义,dp[i][j]表示i个鸡蛋,尝试j次,在最坏情况下能确定的楼层数。

  • 假如碎了还有i-1个鸡蛋,还有j-1次尝试机会,就是dp[i-1][j-1]
  • 假如没碎还有i个鸡蛋,还有j-1次机会,就是dp[i][j-1]

无论碎还是没碎,尝试的这一次已经试探出了一层楼所以要加一
d p [ i ] [ j ] = d p [ i − 1 ] [ j − 1 ] + d p [ i ] [ j − 1 ] + 1 dp[i][j] = dp[i-1][j-1]+dp[i][j-1]+1 dp[i][j]=dp[i1][j1]+dp[i][j1]+1

class Solution {
public:
    int superEggDrop(int K, int N) {
        int dp[K+1][N+1];  // dp[i][j]表示i个鸡蛋,尝试j次,最坏情况下可以试探出的最高楼层
        memset(dp,0,sizeof(dp));
        int ans = 0;  // 尝试次数
        while(dp[K][ans]<N){
            ans ++;
            for(int k=1;k<=K;k++) dp[k][ans] = dp[k-1][ans-1]+dp[k][ans-1]+1;
        }
        return ans;
    }
};

时间复杂度: O ( K N ) O(K\sqrt N)\quad O(KN )空间复杂度: O ( K N ) O(KN) O(KN)

逆向思维 空间优化版本 [ 3 ] ^{[3]} [3]

又因为第 j j j次尝试只和第 j − 1 j-1 j1次尝试结果相关,因此可以只用一维数组。

class Solution {
public:
    int superEggDrop(int K, int N) {
        int dp[K+1];
        memset(dp,0,sizeof(dp));
        int ans = 0;  // 尝试次数
        while(dp[K]<N){
            ans ++;
            for(int k=K;k>0;k--)  // 从后往前计算
                dp[k] = dp[k-1]+dp[k]+1;
        }
        return ans;
    }
};

时间复杂度: O ( K N ) O(K\sqrt N)\quad O(KN )空间复杂度: O ( K ) O(K) O(K)

Ref

[1] 经典动态规划问题:高楼扔鸡蛋
[2] 2004 - 朱晨光:《优化,再优化!——从《鹰蛋》一题浅析对动态规划算法的优化》
[3] 看到了提交第一的(java),真是把dp用的淋漓尽致。。。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值