鸡蛋掉落问题——经典的算法题目,自己真的想不到

本文是leetCode的一道题,鸡蛋掉落。这是谷歌的精典面试题,因为太过经典,现在谷歌不用它来面试了。

题目描述如下:

你将获得 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

刚开始我也是不知道从哪里入手,看了看官方的解析,还是不懂。我想啊想啊,突然之间,懂了。特地写一篇文章来记录下。

从最简单的开始——只有一个鸡蛋

如果你手上只有一个鸡蛋,啥也甭想,只能从一楼开始,逐层往上试,比如总共10层楼。1楼试了没碎,试2楼,直到第5层碎了,那 F=5。如果第10层还没碎,那 F=10。如果非要从第6层开始试,呱唧碎了,这时只能知道 F<= 6,具体是几,因为没鸡蛋,没法试了。所有一个鸡蛋的情况下,要确认F是第几层,10次一定可以试出来。即一个鸡蛋的情况下,最小次数=楼层数

稍微复杂些的——有两个鸡蛋

N=1 不用想 试一次,F=0或F=1;
N=2 也不用想 试两次, F可能的值是0,1,2。
N=3 试两次,先在二楼试一次,如果没碎,试下三楼,如果碎了,试下一楼。这个也不难想。 F可能的值是0,1,2,3
N=4 试三次,这个依然可用想出来。那N=9呢,估计大脑都转不过来了吧。所有要找规律。

思路转变,集中精力。两个鸡蛋五层楼

N=5 假如先在第3层试。假如没碎,就是还有2个鸡蛋,还剩2层楼(4层5层),假如碎了,就是1个鸡蛋2层楼(1层2层)。前者就相当于N=2,两个鸡蛋,这个我们讨论过了,需要两次。后者相当于1个鸡蛋,两层楼。也讨论过了,不用想,也是两次。 N=5,两个鸡蛋,第一次在第3层试,之后不论结果如何,都还需要两次。也就是说第二步2和2,取大值。总共需要试(1+2=3)次

这个如果没有理解,不要往下看,倒回去重新看。

N=5 假如先在第4层试,也是同样的道理。子问题就是,2个鸡蛋1层楼 和 1个鸡蛋三层楼。前者需要试1次,后者需要试3次。那需要取大值,加上第一次试的那次。也就是说第二步1和3,取大值。总共需要试(1+3=4)次

N=5 假如先在第2层试。子问题就是,2个鸡蛋3层楼和1个鸡蛋1层楼。前者需要试2次,后者需要试1次。需要取大值,第二步1和2,取大值。总共需要(1+2=3)次

N=5 假如先从第5层试,第二步0和4,取大值。总共需要(1+4=5)次,从第1层试第二步0和2,取大值。总共需要(1+2=3)次

写到这里,已经列举了N=5,两个鸡蛋的所有情况,得出的结果就是,至少试3次,一定可以确认F的值

同样,两个鸡蛋,N=9时,至少4次确认F的值。比如第一次在第四层开始试,子问题就是 1个鸡蛋三层楼,两个鸡蛋5层楼,总共需要试(1+3=4)次

代码如何写呢。最优解的问题,且一个问题可拆分为两个子问题,可以用动态规则来解决。

刚刚N=5的例子,

第一层试,子问题是(02)取最大值,
第二层试,子问题是(12)取最大值,
第三层试,子问题是(22)取最大值,
第四层试,子问题是(31)取最大值,
第五层试,子问题是(40)取最大值,
最后是从这五个结果中挑出最小的一个。

数字拆分来看,只看前半部分 0,1,2,3,4 这是递增的。后半部分2,2,2,1,0是递减的。不严谨的想一下,最小的应该在中间附近。如何用代码来实现,这是最难理解的。结合代码来理解。代码实现中,用二分查找来找出最小值,时间复杂度为O(logn)。当然也可以不用二分查找,从1到n逐个比较,来得到最小值。

class Solution {
    public int superEggDrop(int K, int N) { // K指鸡蛋个数, N指楼层数
        if(K == 0 || N == 0) return 0;
        int[][] record = new int[K+1][N+1]; // 记录已计算的结果
        return dp(record, K, N);
    }
    private int dp(int[][] record, int K, int N){
        if(K == 1) return N; // 只有一个鸡蛋,楼层是几,就试几次。
        if(N == 0) return 0;
       
        if(record[K][N] > 0){// 没计算过的是默认值0
            return record[K][N];
        }
        int low = 1;
        int high = N;
        int temp = Integer.MAX_VALUE; // 记录K个鸡蛋,N层楼,试几次
        while(low <= high){
            int mid = (high - low)/2 + low;
            int t1 = dp(record, K - 1, mid -1); // 鸡蛋碎了,子问题是K-1个鸡蛋,mid楼层之下
            int t2 = dp(record, K, N - mid); // 鸡蛋没碎,子问题是K个鸡蛋,mid楼层之上
            // N=9 K=2,先在第五层试,子问题(4,2),下一轮循环,再在第三层试(2,4),逐渐缩小范围
            if(t1 > t2){ 
                high = mid -1;
                temp = Math.min(temp, t1+1); 
                // t1+1,相当于前文说的*第二步1和3,取大值。总共需要试(1+3=4)次。每次将最小值赋值给temp.
            }else{
                low = mid + 1;
                temp = Math.min(temp, t2+1);
            }
        }
        record[K][N] = temp;
        return record[K][N];
    }    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值