算法面试难题-1-鸡蛋掉落问题

问题描述

在这里插入图片描述

方法1——动态规划(超时)
class Solution {
public:
    int superEggDrop(int K, int N) {
        const int INF=0x3f3f3f3f;
        //dp[i][j]:一共有i层楼梯的情况下,使用j个鸡蛋的最少实验的次数
        vector<vector<int>>dp(N+1,vector<int>(K+1,0));

        //初始化
        for(int i=0;i<=N;i++){
            for(int j=0;j<=K;j++){
                dp[i][j]=INF;
            }
        }

        //第0行:楼层为0的时候,不管鸡蛋个数多少,都测试不出鸡蛋的F值,故全为0
        for(int j=0;j<=K;j++){
            dp[0][j]=0;
        }

        //第1行:楼层为1的时候,0个鸡蛋的时候,仍0次,1个以及1个鸡蛋以上只需仍1次
        dp[1][0]=0;
        for(int j=1;j<=K;j++){
            dp[1][j]=1;
        }
        
        //第0列:鸡蛋个数为0的时候,不管楼层为多少,也测试不出鸡蛋的F值,故全为0
        //第1列:鸡蛋个数为1的时候,这是一种极端情况,要测试出F值,最少次数就等于楼层高度
        for(int i=0;i<=N;i++){
            dp[i][0]=0;
            dp[i][1]=i;
        }

        //从第2行,第2列开始填表
        for(int i=2;i<=N;i++){
            for(int j=2;j<=K;j++){
                for(int k=1;k<=i;k++){
                    //碎了,就需要往低层继续扔:层数少1,鸡蛋也少1
                    //不碎,就需要往高楼继续仍:层数是当前层到最高层的距离差,鸡蛋数量不少
                    //两种情况都做了一次尝试,所以加1
                    dp[i][j]=min(dp[i][j],max(dp[k-1][j-1],dp[i-k][j])+1);
                }
            }
        }

        return dp[N][K];
    }
};
复杂度分析:
  • 时间复杂度:O(N2K),三层for循环,每层循环都是线性的。
  • 空间复杂度:O(NK),表格大小。
方法2——动态规划+二分

这里需要盯着[状态转移方程]使劲看:

	dp[i][j]=min(max(dp[k-1][j-1],dp[i-k][j])+1);

[状态转移方程]里最外层的变量是k,它枚举了扔下鸡蛋的楼层的高度,这里它是自变量,将其余的i和j视为常数:

  • dp[k-1][j-1]:根据语义,k增大的时候,楼层大小越大,它的值就越大;
  • dp[i-k][j]:根据语义,k增大的时候,楼层大小越小,它的值就越小。

可以得出一个是单调不减的(dp[k-1][j-1],下面红点),一个是单调不增的(dp[i-k][j],下面绿星),并且它们的值都是整数。
我们使用了一组数据,制作成图标(每次取数据都取最后一行最后一列的那个单元格计算的数据)。
情况1:最低点只有1个点
在这里插入图片描述
情况2:最低点是若干个重合的点
在这里插入图片描述
情况3:最低点不重合,但是两边的值一样
在这里插入图片描述
从图上可以看出:二者的较大值的最小点在它们交汇的地方。那么有没有可能不交汇,当前有可能(上面第3张图),二者较大值的最小者一定出现在画成曲线段交点的两侧,并且二者的差值不会超过1,也就是如果没有重合的点,两边的最大值是一样的(从图上看出来的,没有严格证明),因此取左侧和右侧两点中的一点都可以,不失一般性,可以取左边的那个点的k。
也就是找到使得dp[i-k][j]<=dp[k-i][j-1]最大的那个k值即可。这里使用二分查找算法。关键在于dp[i-k][j]>dp[k-i][j-1]的时候,k一定不少我们要找的,根据这一点写出二分的代码。

class Solution {
public:
    int superEggDrop(int K, int N) {
        const int INF=0x3f3f3f3f;
        //dp[i][j]:一共有i层楼梯的情况下,使用j个鸡蛋的最少实验的次数
        vector<vector<int>>dp(N+1,vector<int>(K+1,0));

        //初始化
        for(int i=0;i<=N;i++){
            for(int j=0;j<=K;j++){
                dp[i][j]=INF;
            }
        }

        //第0行:楼层为0的时候,不管鸡蛋个数多少,都测试不出鸡蛋的F值,故全为0
        for(int j=0;j<=K;j++){
            dp[0][j]=0;
        }

        //第1行:楼层为1的时候,0个鸡蛋的时候,仍0次,1个以及1个鸡蛋以上只需仍1次
        dp[1][0]=0;
        for(int j=1;j<=K;j++){
            dp[1][j]=1;
        }
        
        //第0列:鸡蛋个数为0的时候,不管楼层为多少,也测试不出鸡蛋的F值,故全为0
        //第1列:鸡蛋个数为1的时候,这是一种极端情况,要测试出F值,最少次数就等于楼层高度
        for(int i=0;i<=N;i++){
            dp[i][0]=0;
            dp[i][1]=i;
        }

        //从第2行,第2列开始填表
        for(int i=2;i<=N;i++){
            for(int j=2;j<=K;j++){
                //在区间[1,i]里确定一个最优值
                int left=1;
                int right=i;
                while(left<right){
                    //找dp[k-1][j-1]<=dp[i-mid][j]的最大值k
                    int mid=left+(right-left+1)/2;
                    
                    int breadCount=dp[mid-1][j-1];
                    int noBreakCount=dp[i-mid][j];
                    if(breadCount>noBreakCount){
                        //严格大于的时候一定不是解,此时mid一定不是解
                        //下一轮搜索区间是[left,mid-1]
                        right=mid-1;
                    }else {
                        //这个区间一定是上一个区间的反面,即[mid,right]
                        //注意这个时候取中间数要上取整,int mid=left+(right-left+1)/2;
                        left=mid;
                    }
                }
                //left这个下标就是最优的k值,把它代入转移方程
                dp[i][j]=max(dp[left-1][j-1],dp[i-left][j])+1;
            }
        }

        return dp[N][K];
    }
};
复杂度分析:
  • 时间复杂度:O(NK \log N)O(NKlogN),其中一层循环变成二分查找,复杂度成为对数;
  • 空间复杂度:O(NK)O(NK),表格的大小。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Puzzle harvester

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值