zoukankan      html  css  js  c++  java
  • 鸡蛋掉落问题(lc887)

    鸡蛋掉落-动态规划

    题目描述:

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

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

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

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

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

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

    题目理解

      首先,本题要求的最小的移动次数可以理解为最少的扔鸡蛋次数,也就是说,最少扔几次鸡蛋就一定可以找到楼层F使鸡蛋恰好不被摔坏(再向上一层就摔坏了),鸡蛋碎了不可再用。而 F 的位置并不确定(如果我们知道 F 是几,那就不用扔鸡蛋了),无论 F 为第几层,假设我们都可以在 t 次或小于 t 次扔鸡蛋后找到 F ,那么最小的 t 即为答案。换言之,扔鸡蛋的次数和方式必须可以判断所有楼层是否是 F ,即最少扔几次可以“遍历”N层高的楼。

      如果题目没有限制鸡蛋个数(随便摔),那么采用二分扔鸡蛋方式可以用最少的次数找到 F ,例如有5层楼,首先在第3层扔,如果碎了, F 只能在3层一下,继续向下二分,如果没碎,则向上二分。但是,题目限制了鸡蛋个数,使用二分法可能导致鸡蛋数量不够用,如 N 为9, K 为2,在第5层扔了鸡蛋,碎了,那么只剩一个鸡蛋, F 可能为0-4,为了找到 F ,只能从第一层向上逐层扔最后一个鸡蛋,那么还得扔4次;但如果一开始在第4层扔第一个鸡蛋,无论鸡蛋碎或没碎,都只需要再扔3次就能确定,因此,二分法并不是正确的求解姿势,只有鸡蛋数量足够时才是最好,若数量不够,则第一次扔鸡蛋通常选择在中间楼层以下的某个位置(这不重要)。这道题可以用动态规划的方法求解。

    算法思路 1

      本题的状态量有两个,一是鸡蛋数量 K ,二是楼层 N ,可以使用dp[K][N]来表示 K 个鸡蛋“遍历” N 层楼需要的次数。第一次扔鸡蛋的楼层假设为 x ,如果碎了,还需要再扔dp[K-1][x-1]次,如果没碎,还需要扔dp[k][N-x]次,因此,需要次数为max(dp[K-1][x-1], dp[k][N-x]),而 x 的范围为1<=x<=N,则有关系式:

     dp[K][N] = min(max(dp[K-1][x-1], dp[k][N-x])), 1<=x<=N

      很显然,随着 x 的增加,max(dp[K-1][x-1], dp[k][N-x]) 是先减后增的,因此可以进行剪枝,x 不需要遍历到N。不过,该算法时间复杂度依然为o(KN2), 效率较低。

    c++实现

    int superEggDrop(int K, int N) {
            int time[K+1][N+1];
            for(int i = 1; i <= K; i++)
                for(int j = 1; j <= N; j++){
                    if(i == 1) time[i][j] = j;
                    else if(j <= 2) time[i][j] = j;
                    else{
                           int p = 2;
                           while(p < j-1 && max(time[i-1][p], time[i][j-p-1])
                                    <= max(time[i-1][p-1],time[i][j-p]) ) 
                                    p++;
                           time[i][j] = max(time[i-1][p-1], time[i][j-p])+1;
                     }
                 }
             return time[K][N];
    }            

    算法思路 2

      还有另一种动态规划方法,经上述分析,题目要求解出能够“遍历” N 层楼的最小扔鸡蛋次数 t ,反过来看,也可以求当允许扔鸡蛋的次数为 m 时,在 K 个鸡蛋的情况下,可以“遍历”的楼层数 n,随着 m 的增加,n越来越大,直到 n > N 时,m 即为 t ,因此可以设置dp[m][K]表示有 K 个鸡蛋,允许扔 m 次时可以“遍历”的楼层数。假设扔第一次时(在 x 楼),鸡蛋碎了,那么还有 m-1 次机会,K-1 个鸡蛋,可以继续向下检测dp[m-1][K-1]层楼,如果没有碎,则可以向上检测dp[m-1][K]层楼,因此,dp[m][K] = 1 + dp[m-1][K-1] + dp[m-1][K]。代码实现如下,进行了优化,使用两个一维数组dp1[K+1],dp2[K+1]代替dp[m][K]。

    最坏时间复杂度为o(KN)。

    c++实现

    int superEggDrop(int K, int N) {
            int dp1[K+1] = {0};
            int dp2[K+1] = {0};
            int time = 0;
            while(dp1[K] < N){
                time++;
                for(int i = 1; i <= K; i++){
                    dp2[i] = dp1[i-1] + dp1[i] + 1;
                    dp1[i-1] = dp2[i-1];
                }
                dp1[K] = dp2[K];
            }
            return time;
    }
  • 相关阅读:
    31、状态模式(详解版)
    33、中介者模式(详解版)
    36、备忘录模式(详解版)
    34、迭代器模式(详解版)
    30、责任链模式(职责链模式)详解
    29、命令模式(详解版)
    32、观察者模式(Observer模式)详解
    37、解释器模式(详解版)
    35、访问者模式(Visitor模式)详解
    28、策略模式(策略设计模式)详解
  • 原文地址:https://www.cnblogs.com/zz-zhang/p/12318711.html
Copyright © 2011-2022 走看看