887:鸡蛋掉落

问题描述

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

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

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

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

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

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

示例

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

思路

一开始想的是,卧槽这么简单? 二分啊。甚至连二分我都不用,我直接用logn来计算不就行了??
我脑袋是真的瓦特了。怎么可能这么简单??没有无限个鸡蛋,不能这么玩。
那咋整?用递归。
扔下去一个蛋,就两种结果:

  • 碎了
  • 没碎

碎了的话,代表待查找的范围肯定是在下面这些层。
没碎的话,代表待查找的范围肯定是在上面那些层。

把求解 F 的过程认为是用最好的算法,即使是在最坏的运气下,为了准确得到结果,找到 F 这个值的实验的次数最少是多少。

说了一堆,到底咋找?不会啊,让计算机来模拟吧。我们只需要获得一个结果superEggDrop(int K, int N) 它代表了对于K层(范围值),运气最差时最少要用多少次。(递归)。 怎么做的我们管吗?不管。那我们要管点啥呢?想一下:

  • 如果鸡蛋的个数是0,则返回0. (没有鸡蛋怎么扔)
  • 如果鸡蛋的个数是1,则返回N. (只有1个鸡蛋,只能1层1层扔,运气最差时就是层高)
  • 如果楼层高度为1, 则返回1. 因为只需要扔一次。
  • 如果楼层高度为0, 则返回0. 因为不用扔了。

那么,别的情况咋整? 我们可以变化他们,让他们逼近边界条件。 比如,从当前范围的第i层往下扔,则可以先从第一层扔,也可以先从第二层扔。。。也可以先从第N层扔。我们可以用遍历做到这一点。 对于从第i层扔,可以得到俩结果。如果碎了呢,则可以扔当前范围的第1~i-1层了,鸡蛋由于碎掉了则剩余了K-1个。而如果没碎呢,则可以扔第i+1~N这些层了,但是,想一下,如果要用到原来的状态,怎么办?符合逻辑的情况是,我们还剩下N-i层没有做实验, i+1~N可不就是N-i嘛。也就是说,我们每次做实验对应的N是层的范围(range),并不是真的第几层。因为搞明白真正第几层没有意义,层高才能说明问题。为啥呢?因为万一我们做实验得知F在[98,99,100]里,我们也是只需要做3次实验就知道在哪了。而[1,2,3]也是做3次实验,所以只跟范围有关。
接着往下说,碎还是不碎,都会返回一个结果。我们要这个结果的最大值。为啥呢?因为最大值才比较倒霉嘛,不是说了最坏情况下? 而这个最大值要加一,才是当前的值,为啥呢? 因为因为第一个蛋扔在了第i层,也做了一次实验呀。 然后对于从当前范围的第1层扔到第N层,统计出来一个最坏情况下的最好的值,这就是结果了。(方法一) 超时了,但是思路正确。

把方法一改成dp,即成了方法二。根据方法一的解释,我们很容易就得到了状态转移方程.(方法二)

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

dp竟然超时了你敢信? 这可咋整?
我们发现,dp[i-1][k-1]和dp[i][j-k]竟然是单调的。而且具有相反的单调性。那么,我们根据二分找出来他们相等的值,或者是正好一上一下的值的那个较大的值,不就得了?
(方法三)

方法一

class Solution {
    public int superEggDrop(int K, int N) {
        if(K == 0) return 0;
        if(K == 1) return N;
        if(N == 0) return 0;
        if(N == 1) return 1;
        int min = N;
        for(int i = 1; i <= N; i++){
            min = Math.min(min,1+Math.max(superEggDrop(K-1,i-1),superEggDrop(K,N-i)));
        }
        return min;
    }
}

方法二

class Solution {
    public int superEggDrop(int K, int N) {
        int[][] dp = new int[K+1][N+1];
        for(int i = 1; i < dp.length; i++){
            dp[i][1] = 1;
        }
        for(int i = 1; i < dp[0].length; i++){
            dp[1][i] = i;
        }
        for(int i = 2; i <= K; i++){
            for(int j = 2; j <= N; j++){
                dp[i][j] = j;
                for(int k = 1; k <= j; k++){
                    System.out.println(Math.max(dp[i-1][k-1],dp[i][j-k]));
                    dp[i][j] = Math.min(dp[i][j],Math.max(dp[i-1][k-1],dp[i][j-k])+1);
                }
            }
        }
        return dp[K][N];
    }
}

方法三

class Solution {
    public int superEggDrop(int K, int N) {
        int[][] dp = new int[K+1][N+1];
        for(int i = 1; i < dp.length; i++){
            dp[i][1] = 1;
        }
        for(int i = 1; i < dp[0].length; i++){
            dp[1][i] = i;
        }
        for(int i = 2; i <= K; i++){
            for(int j = 2; j <= N; j++){
                int left = 1,right = j;
                while(left <= right){
                    int mid = left + (right-left)/2;
                    int leftVal = dp[i-1][mid-1];
                    int rightVal = dp[i][j-mid];
                    if(leftVal == rightVal){
                        left = mid;
                        break;
                    }
                    if(leftVal > rightVal){
                        right = mid - 1;
                    }else{
                        left = mid + 1;
                    }
                }
                dp[i][j] = Math.max(dp[i-1][left-1],dp[i][j-left])+1;
            }
        }
        return dp[K][N];
    }
}
发布了464 篇原创文章 · 获赞 21 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_41687289/article/details/105499715