• 2018 年力扣高频算法面试题汇总-难题记录-鸡蛋掉落


    题目描述:

    你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N  共有 N 层楼的建筑。

    每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

    你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

    每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。

    你的目标是确切地知道 F 的值是多少。

    无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

    示例 1:

    输入:K = 1, N = 2
    输出:2
    解释:
    鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
    否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
    如果它没碎,那么我们肯定知道 F = 2 。
    因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。
    

    示例 2:

    输入:K = 2, N = 6
    输出:3
    

    示例 3:

    输入:K = 3, N = 14
    输出:4
    

    提示:

    1. 1 <= K <= 100
    2. 1 <= N <= 10000

    要完成的函数:

    int superEggDrop(int K, int N) 

    说明:

    1、这道题给定K个鸡蛋和N层楼,想要测试鸡蛋壳的保护能力,最高从第几层楼摔下来鸡蛋不会碎,而从高一层楼摔下来鸡蛋就会碎。最高不会碎的这一层称为第F层。

    现在F层具体是哪一层不知道,而你有K个鸡蛋,一共有N层楼,问需要尝试多少次扔鸡蛋才能知道F是多少。

    如果鸡蛋摔碎了就不能再用,如果鸡蛋没碎那么还可以继续扔鸡蛋下楼做测试。

    鸡蛋个数K在1到100的闭区间,楼层个数N在1到10000的闭区间,F是0到N的闭区间。

    2、(下面的思路分享很长,但是思路很详细,基本记录了笔者从粗浅到最终解决方案的整个思考过程)

    题意有点绕,举个例子说明一下。

    假设你只有1个鸡蛋,2层楼,那么你只能从一楼开始扔,扔下去如果摔碎了,那么说明F是0,因为如果F是1的话,从一楼摔下去鸡蛋不会碎的。此时只需要1次扔鸡蛋。

    如果从一楼摔下去不会碎,那么F是1或者2,那么就尝试从二楼扔下来,如果碎了,那么F是1;如果没碎,那么F是2。此时需要扔2次鸡蛋。

    所以在不知道F具体是多少的情况下,最小需要扔2次鸡蛋才能确定F是多少。

    有的同学可能会问,为什么只能从一楼开始?不能从二楼开始吗?

    二楼扔下去如果没碎,那么还好,F肯定是2。

    但是如果碎了呢?F有可能是1,也有可能是0,而你唯一的一个鸡蛋碎了,测试失败。

    所以在只有1个鸡蛋的情况下,只能老老实实从一楼开始测试,逐渐增加楼层高度。

    如果有2个鸡蛋,100层楼呢,最少的扔鸡蛋次数是多少?

    笔者最开始的想法是,可以先拿一个鸡蛋做测试,尽可能减少测试空间。比如在50层抛下来,如果碎了那么测试空间就是[1,49];如果没碎,那么测试空间是[51,100]。

    这里解释一下为什么碎了,测试空间就是[1,49];没碎,测试空间就是[51,100]。

    如果鸡蛋碎了,那么只剩下一个鸡蛋,F的取值范围是[0,49],我们唯一的鸡蛋只能从一楼开始扔。按照我们的经验,1个鸡蛋49层楼,那么一共需要扔49次才能确定F的具体值。

    也就是说当F的取值范围是[0,49]的时候,扔鸡蛋的测试空间是[1,49]。

    那么如果鸡蛋没碎,F的取值范围是[50,100],那么扔鸡蛋的测试空间就是[51,100],从51楼开始扔,一直到100楼扔,必定可以确定F的具体值。

    笔者这种想法比较直接,但是扔鸡蛋的次数不是最少的。

    比如F是49,在50层扔下去,鸡蛋碎了,只剩下一个鸡蛋,要从1楼开始扔,扔49次到达49层,最终确定F的值。

    一共需要扔1+49次。

    第二个鸡蛋扔49次也太憋屈了吧!第一个鸡蛋只发挥了一次价值就碎了。

    那我们让两个鸡蛋发挥价值能发挥得均衡一点。

    100层楼,开方,10,第一个鸡蛋尝试10次来确定测试区间,第二个鸡蛋也只需要在确定的小区间中尝试。

    这种方法的最坏情况就是F为99,第一个鸡蛋10层扔一下,20层扔一下,……100层扔一下,一共需要10次。确定了F的取值范围是[90,99]。

    那么第二个鸡蛋从91开始尝试,一直扔到99,一共需要9次。

    所以采取这种均衡的方案,最坏情况只需要10+9=19次,比起二分的方法好多了。

    但是这种均衡的方案也不是最佳的,同学们可以参考一下这篇文章:https://juejin.im/post/5b98785de51d450e71250aab

    具体到这道题,我们不再是只有2个鸡蛋和100层楼了,我们现在有K个鸡蛋和N层楼,要求最少的扔鸡蛋次数。

    其实换个思路,每次扔鸡蛋下楼,不就是碎和没碎两种结果吗。

    假设我们在X层扔,如果碎了,那么还有K-1个鸡蛋,X-1层楼待验证;如果没碎,那么还有K个鸡蛋,N-X层楼待验证。这个时候我们已经扔了一次鸡蛋了。

    所以其实我们可以把问题转化为两个子问题,K个鸡蛋N层楼最少的扔鸡蛋次数=K-1个鸡蛋X-1层楼最少的扔鸡蛋次数+1,或者是=K个鸡蛋N-X层楼最少的扔鸡蛋次数+1。

    这两个子问题肯定有一个的扔鸡蛋次数比较大,我们要取那个大的,所以列出式子就是

    record[K][N] = max ( record[K-1][X-1] , record[K][N-X] ) + 1

    X不确定要取多少,经过二分法和均衡法的比较,我们发挥二分的方法也不是最佳的……那要不X就从1到N都试一下吧!如果在某一层扔,可以求得record[K][N] = max ( record[K-1][X-1] , record[K][N-X] ) + 1是最小的,那么我们就能确定X在这一层扔是最佳方案。

    所以式子最终可以写成record[K][N] = min ( max ( record[K-1][X-1] , record[K][N-X] ) + 1 ) , 1<=X<=N

    也就是说,这道题可以用分治法的思想来做,把一个问题分成两个子问题,最终把两个子问题的解汇总,得到原问题的解。

    同时,由于record记录的这些数值要多次使用,所以为了减少时间复杂度,我们就不用分治法,改用动态规划的查表法来做。

    完美符合凌应标老师在课上多次强调的动态规划三个特点哈哈哈:

    1、最优子结构性质——原问题的最优解可以由多个子问题的最优解得到

    2、重复子问题(分治法与动态规划的最大区别)

    3、子问题的个数有限

    动态规划的代码如下:

        int superEggDrop(int K, int N) 
        {
            vector<vector<int>>record(K+1,vector<int>(N+1,0));//需要从0个鸡蛋或者0层楼开始算起,所以申请了K+1行N+1列的空间 
            for(int i=1;i<=K;i++) 
                record[K][1]=1;//如果只有一层楼,那么无论多少个鸡蛋都只需要扔一次鸡蛋 
            for(int i=1;i<=N;i++)
                record[1][i]=i;//如果只有一个鸡蛋,那么有多少层楼就需要扔多少次鸡蛋 
            for(int i=2;i<=K;i++)//从两个鸡蛋两层楼的情况开始算起 
            {
                for(int j=2;j<=N;j++)
                {
                    int t=INT_MAX;
                    for(int l=1;l<=j;l++)
                        t=min(t,max(record[i-1][l-1],record[i][j-l])+1);
                    record[i][j]=t;//记录最小的扔鸡蛋次数到record[i][j]中 
                }
            }
            return record[K][N];//最后返回record[K][N]就是我们要找的扔鸡蛋次数了 
        }

    上述代码没有通过所有的样例测试……因为超时了……

    可以看到三重循环,肯定耗时很多,比如当K=5,N=10000。

    3、如何优化?hhh

    同样还是受上面分享的掘金那篇文章的启发,我们换个角度来想这个问题。

    如果给你K个鸡蛋和M次尝试摔鸡蛋的次数,那么你最多可以测算出多高的楼层,无论F具体是在哪一层。

    假设可以测算的最高楼层是N,那么题意也就是说给K个鸡蛋和M次次数,必定可以测算得到N层楼的F的具体值。

    那还是分治法的思路来看,在某一层X摔下去,如果碎了,那么只剩下M-1次次数和K-1个鸡蛋,而用这剩下的M-1次次数和K-1个鸡蛋,必定可以测算得到X-1层的F的具体值。

    如果没碎,那么还剩下M-1次次数和K个鸡蛋,而用这M-1次次数和K个鸡蛋,必定可以测算得到N-X的楼层的F的具体值。

    我们用record[M][K]代表,用M次次数和K个鸡蛋,最高能测算的楼层高度。

    record[M-1][K-1]代表,用M-1次次数和K-1个鸡蛋,最高能测算的楼层高度。

    record[M-1][K]代表,用M-1次次数和K个鸡蛋,最高能测算的楼层高度。

    可以有record[M][K] = record[M-1][K-1] + record[M-1][K] + 1

    +1是因为加上当前层的楼层高度。

    这个式子理解得不是很清晰的话,同学们自己再琢磨一下,再回看3中的话。这个式子想要清晰理解需要费些思索。

    可以结合record[2][2]的情况来理解,再试下record[3][2]的情况。

    有了这个式子,我们能求M次次数和K个鸡蛋的情况下,最高能测多少层。

    但题目求的是层数确定了,鸡蛋个数确定了,要求M的具体值。

    其实一样的,比如确定鸡蛋个数K是3,楼层高度N是14。

    假如只有一次尝试次数,3个鸡蛋,那么最高也就能测一层。达不到14的高度。

    如果两次尝试次数,3个鸡蛋,那么record[2][3] = record[1][2] + record[1][3] + 1。

    record[1][2]和record[1][3]代表一次尝试次数,那么最高只能测一层,所以上式结果是3。同样达不到14的高度。

    如果三次尝试次数,3个鸡蛋,那么record[3][3] = record[2][2] + record[2][3] + 1=3+3+1=7。同样达不到14的高度。

    如果四次尝试次数,3个鸡蛋,我们会发现record[4][3] = record[3][2] + record[3][3] + 1=6+7+1=14。刚好达到14的高度。

    所以其实我们只需要不断尝试下去,最终尝试第M次的时候,发现record[M][K]>=N,那么就可以了。

    具体写代码的时候,发现我们没办法提前确定M的次数,所以没办法定义一个M行K列的vector来存储数据。

    但我们发现其实每次增大尝试次数的时候,都是基于上一次尝试的结果来求解。

    所以我们可以只定义一个1行K列的vector,然后不断地更新这一行vector的数值,直到在某次更新之后vector[K]>=N。

    具体之后的代码如下:

        int superEggDrop(int K, int N) 
        {
            vector<int>record(K+1,1);//包含0个鸡蛋的情况,所以需要申请K+1个空间。只尝试一次的时候,无论多少个鸡蛋,最高都只能测1层
            record[0]=0;//0个鸡蛋的情况特殊化处理,为0
            int move=2;//如果尝试2次
            while(record[K]<N)//当record[K]大于等于N的时候就退出循环
            {
                for(int i=K;i>=1;i--)//从vector的后面开始更新,这样不影响其他位置的vector元素的更新
                    record[i]=record[i]+record[i-1]+1;
                move++;//move+1,再尝试一次
            }
            return move-1;//返回需要的尝试次数
        }

    可以说是非常简洁了。题目十分复杂,但是想明白之后,写一个二重循环就可以解决这道题目。

    花了两个小时写出这篇思路比较详细的文章,作为一个自我记录,也希望可以给后来者些许启发。

    因为时间紧,排版和语言组织什么的可能不是很好,有哪里写得不好欢迎同学们在评论区提出~

  • 相关阅读:
    HDU1007
    DFA
    netstat
    Sangfor
    JS 基础逻辑关系
    正则表达式
    JS中的DOM
    HTML、CSS、JS面试题
    JS作用域和作用域链
    JS String与正则表达式
  • 原文地址:https://www.cnblogs.com/chenjx85/p/10523857.html
Copyright © 2020-2023  润新知