zoukankan      html  css  js  c++  java
  • 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;//返回需要的尝试次数
        }

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

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

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

  • 相关阅读:
    HDU 3572 Task Schedule(拆点+最大流dinic)
    POJ 1236 Network of Schools(Tarjan缩点)
    HDU 3605 Escape(状压+最大流)
    HDU 1166 敌兵布阵(分块)
    Leetcode 223 Rectangle Area
    Leetcode 219 Contains Duplicate II STL
    Leetcode 36 Valid Sudoku
    Leetcode 88 Merge Sorted Array STL
    Leetcode 160 Intersection of Two Linked Lists 单向链表
    Leetcode 111 Minimum Depth of Binary Tree 二叉树
  • 原文地址:https://www.cnblogs.com/chenjx85/p/10523857.html
Copyright © 2011-2022 走看看