887. 鸡蛋掉落问题

4月11日的leetcode每日一题,这是一道谷歌经典的面试题,题目如下:

在这里插入图片描述

数据范围

1 <= K <= 100

1 <= N <= 10000

题目

在官方题解处看到了这么一段描述。。。
在这里插入图片描述
是不是题目都读不懂。。。这道题读起来有浓浓的机翻味,这里重新组织一下语言:

有K个软硬程度一样的鸡蛋,和N层楼,你不知道这些蛋从哪里扔下去会碎。你只能一个个试,每次你可以取一个鸡蛋从X层扔下(1 <= X <= N)。假设鸡蛋从F层及以下掉落不会碎,但是F层以上都会碎。请问想要得到确切的F,最少需要你移动几次。

注:鸡蛋扔下去没碎可以捡回来继续扔,碎了就不能用了。不包括下去捡蛋的移动。

分析

这是经典动态规划问题。

状态表示: 首先定义f(i, j),表示有i个鸡蛋j层楼时,确定F值最少的移动次数。

状态转移: 假设我们在x层楼扔下鸡蛋,有两种情况发生

  1. 鸡蛋碎了,则还有i-1个鸡蛋,F值肯定在x楼下面(1~x-1),故问题就变成了f(i-1, x-1)

  2. 鸡蛋没碎,则还有i个鸡蛋,F值肯定在x楼上面(x+1~j),故问题规模就缩小成了f(i, j-x)

  3. 最后得到状态转移方程:

    f ( i , j ) = 1 + m i n ( m a x ( f ( i − 1 , x − 1 ) , f ( i , j − x ) ) ) , 1 < = x < = j f(i, j) = 1 + min(max(f(i - 1, x - 1), f(i, j - x))), 1<= x <= j f(i,j)=1+min(max(f(i1,x1),f(i,jx))),1<=x<=j

    为什么是min(max())呢?

    首先要找到鸡蛋碎了和没碎中的最坏情况,因为你不能保证鸡蛋都是碎的,或者鸡蛋都没碎,假设每层鸡蛋都碎了,就会有最坏情况,全部楼层走一遍。只有最坏情况才能确定F值,故max()

    其次,在所有楼层上都扔一遍,暴搜一下,得到最少需要移动的次数,故min()

    最后,+1是当前扔鸡蛋的操作,不管碎没碎都算是移动了一次。

时间复杂度: 状态表示是 O ( K ∗ N ) O(K*N) O(KN),状态计算的复杂度是 N N N,故整体的时间复杂度是 O ( K N 2 ) O(KN^2) O(KN2)

​ 由题目的数据范围可知 K N 2 = 100 ∗ 1 e 4 ∗ 1 e 4 = 1 e 10 ​ KN^2 = 100 * 1e4 * 1e4 = 1e10​ KN2=1001e41e4=1e10。超时了!!!

二分查找

二分主要解决x的搜索问题。先找上面两个函数有没有单调性,设:
T 1 ( x ) = f ( i − 1 , x − 1 ) T 2 ( x ) = f ( i , j − x ) T_1(x) = f(i-1, x-1) \\ T_2(x) = f(i, j-x) T1(x)=f(i1,x1)T2(x)=f(i,jx)
f(K, N)这个函数可知,N越大,即楼层越高,确定F值所需的次数肯定不会变小,故函数是单调递增的。

T 1 ( x ) ​ T_1(x)​ T1(x)是单调递增函数, T 2 ( x ) ​ T_2(x)​ T2(x)是单调递减函数。 m a x ( T 1 ( x ) , T 2 ( x ) ) ​ max(T_1(x), T_2(x))​ max(T1(x),T2(x))可以用下图函数蓝色的那段表示出来:

fig1

那么 m i n ( m a x ( T 1 ( x ) , T 2 ( x ) ) ) min(max(T_1(x), T_2(x))) min(max(T1(x),T2(x)))就是红色那个交点,由于该题的x是离散的,所以答案应该在距离交点最近的左右两个整数点x0和x1上,只要求 m i n ( m a x ( T 1 ( x 0 ) , T 2 ( x 0 ) ) , m a x ( T 1 ( x 1 ) , T 2 ( x 1 ) ) ) min(max(T_1(x0), T_2(x0)), max(T_1(x_1), T_2(x_1))) min(max(T1(x0),T2(x0)),max(T1(x1),T2(x1)))就行了,这样就把x的搜索从O(N)缩小到了O(logN),易证:x1 = x0 + 1。

所以我们只需要找到x0就行了,x0的性质是T1(x0) <= T2(x0)最大的那个点,二分的写法如下:

int l = 1, r = j;
while(l < r){
    int mid = l + r + 1 >> 1;
    if(T1(mid) <= T2(mid)) l = mid;
    r = mid - 1;
}

int x0 = l, x1 = l + 1;
int max_x1, max_x2 = 1e9;
max_x1 = max(f[i - 1][x0 - 1], f[i][j - x0]);
if(x1 <= j){
    max_x2 = max(f[i - 1][x1 - 1], f[i][j - x1]);
}
f[i][j] = 1 + min(max_x1, max_x2);

在判断二分条件时,要根据x0的性质来判断,如果x0的性质是T1(x0) < T2(x0),则二分条件也要写成<,找到的x是不包括交点的交点左边第一个点。

边界

这道题的边界很重要,我在边界上面卡了很久,考虑两个边界:

  1. j == 0时,f(i, 0) = 0,因为0层楼的情况下,不管有几个蛋,移动次数都是0。
  2. i == 1时,f(1, j) = j,当只有一个蛋的时候,就只能从1楼往上线性的走,一个一个扔蛋,扔到会碎的那一层为止,故移动次数是j。

最后代码如下:

class Solution {
public:

    int superEggDrop(int K, int N) {
        int f[K + 1][N + 1];
        memset(f, 0, sizeof f);
        for(int i = 0; i <= N; i++) f[1][i] = i;

        for(int i = 2; i <= K; i++){
            for(int j = 1; j <= N; j++){
                int l = 1, r = j;
                while(l < r){
                    int mid = l + r + 1 >> 1;
                    if(f[i - 1][mid - 1] <= f[i][j - mid]) l = mid;
                    else r = mid - 1;
                }
                
                int x0 = l, x1 = l + 1;
                int max_x1, max_x2 = 1e9;
                max_x1 = max(f[i - 1][x0 - 1], f[i][j - x0]);
                if(x1 <= j){
                    max_x2 = max(f[i - 1][x1 - 1], f[i][j - x1]);
                }
                f[i][j] = 1 + min(max_x1, max_x2);
            }
        }
        return f[K][N];
    }
    
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值