887.鸡蛋掉落_hard

题目

你将获得 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 <= K <= 100
1 <= N <= 10000

来源:力扣(LeetCode)887.鸡蛋掉落
链接:https://leetcode-cn.com/problems/super-egg-drop


分析

也就是让你找: 摔不碎鸡蛋的最高楼层 F

但什么叫「最坏情况」下「至少」要扔几次呢?

 

假如:现在先不管鸡蛋个数的限制,有 8 层楼,你怎么去找鸡蛋恰好摔碎的那层楼?

最原始的方式就是线性扫描:我先在 1 楼扔一下,没碎,我再去 2 楼扔一下,没碎,我再去 3 楼……

以这种策略,最坏情况应该就是我试到第 8 层鸡蛋也没碎(F = 8),也就是我扔了 8 次鸡蛋。

 

假如:只给你 1 个鸡蛋,8 层楼,

你如果敢用二分的话,直接去第 4 层扔一下,如果鸡蛋没碎还好,

但如果碎了,你就没有鸡蛋继续测试了,无法确定鸡蛋恰好摔不碎的楼层 F 了。

这种情况下只能用线性扫描的方法,算法返回结果应该是 8。


我们选择在第 i 层楼扔了鸡蛋之后,可能出现两种情况:

1. 鸡蛋碎了,

2. 鸡蛋没碎。

如果鸡蛋碎了,那么鸡蛋的个数 K 应该减一,搜索的楼层区间应该从 [1..N] 变为 [1..i-1]i-1 层楼;

如果鸡蛋没碎,那么鸡蛋的个数 K 不变,搜索的楼层区间应该从 [1..N] 变为 [i+1..N]N-i 层楼。

 

因为我们要求的是最坏情况下扔鸡蛋的次数,

所以鸡蛋在第 i 层楼 碎没碎,取决于哪种情况的结果更大

 


解法1(超出时间限制)

// 语言:C, 超出时间限制

// 提示说了,egg最多100个,楼层最多10000层
int arr[101][10001] = {0};

int superEggDrop(int eggNum, int totalFloor){
    // 只有一个鸡蛋时,只能第一层开始,逐层往上试
    if (eggNum == 1) {
        return totalFloor;
    }
    // 0层, 无需实验
    if (totalFloor <= 0) {
        return 0;
    }

    // 充顶、最多也就是线性查找,逐层试的情况
    int result = totalFloor;

    // 先从备忘录中取结果,如果有记录,则直接返回
    if (arr[eggNum][totalFloor]) {
        return arr[eggNum][totalFloor];
    }

    // 开始
    for (int k = 1; k <= totalFloor; k++) {
        // 在第k层扔了一次
        int testNum = 1;
        // 结果之一是: 在第k层,鸡蛋碎了: 鸡蛋少一个,后面要测的楼层范围也减少
        int caseA_eggBroken = superEggDrop(eggNum - 1, k - 1);
        // 结果之二是: 在第k层,鸡蛋没碎: 鸡蛋数不变,后面要测的楼层范围是totalFloor - k
        int caseB_eggOk = superEggDrop(eggNum, totalFloor - k);

        // 最坏的情况,那么就是caseA 和 caseB较大的情况; 此时测试的次数最多
        int tmpResult = testNum + maxFunc(caseA_eggBroken, caseB_eggOk);

        // 至少要扔多少次, 取较小的那个
        result = minFunc(result, tmpResult);
    }

    // 备忘录用起来
    arr[eggNum][totalFloor] = result;

    return result;
}

运行结果如下:超时了

这个算法的时间复杂度是多少?

动态规划算法的时间复杂度就是 子问题个数 × 函数本身的复杂度

 

函数本身的复杂度就是忽略递归部分的复杂度,

这里 dp 函数中有一个 for 循环,所以函数本身的复杂度是 O(N)。

 

子问题个数也就是不同状态组合的总数,显然是两个状态的乘积,也就是 O(KN)。

所以算法的总时间复杂度是 O(K*N^2), 空间复杂度 O(KN)。

 


分析

 

上面的for循环之所以能用二分搜索,

是因为状态转移方程的函数图像具有单调性,可以快速找到最值

 

根据 dp(K, N) 数组的定义(有 K 个鸡蛋面对 N 层楼,最少需要扔几次),

很容易知道 K 固定时,这个函数一定是单调递增的

无论你策略多优秀,楼层增加测试次数一定要增加。

 

那么注意 dp(K - 1, i - 1)dp(K, N - i) 这两个函数,

其中 i 是从 1 到 N 单增的,

 

如果我们固定 KN把这两个函数看做关于 i 的函数,

前者 dp(K - 1, i - 1) 随着 i 的增加应该是单调递增的,

而后者dp(K, N - i)  随着 i 的增加应该是单调递减的

 

先求二者的较大值,再求这些最大值之中的最小值,其实就是求这个交点


解法2:用二分查找代替for循环

// 通过语言:c

// 提示说了:egg最多100个,楼层最多10000层
int arr[101][10001] = {0};

int minFunc(int a, int b) {
    if (a > b) {
        return b;
    } else {
        return a;
    }
}

int superEggDrop(int eggNum, int totalFloor){
    // 只有一个鸡蛋时,只能第一层开始,逐层往上试
    if (eggNum == 1) {
        return totalFloor;
    }
    // 0层, 无需实验
    if (totalFloor <= 0) {
        return 0;
    }

    // 充顶、最多也就是线性查找,逐层试
    int result = totalFloor;

    // 先从备忘录中取结果,如果有记录,则直接返回
    if (arr[eggNum][totalFloor]) {
        return arr[eggNum][totalFloor];
    }

    // 开始
    int low = 1; 
    int high = totalFloor;

    // 1表示扔出了一次
    int testNum = 1;

    while (low <= high) {
    	// 用二分法, 直接跑到第mid层扔鸡蛋
        int mid = low + (high - low) / 2;

        // 如果鸡蛋碎了: 鸡蛋少一个, 后面要测的楼层范围也减少
        int caseA_eggBroken = superEggDrop(eggNum - 1, mid - 1);

        // 如果鸡蛋没碎: 鸡蛋数不变, 后面要测的楼层范围是totalFloor - mid
        int caseB_eggOk = superEggDrop(eggNum, totalFloor - mid);

        // 最坏的情况, 就是求较大值, 测试次数较多的情况
        if (caseA_eggBroken > caseB_eggOk) {
        	// 如果鸡蛋碎了, 表示F层应该在mid层的下面???
            high = mid - 1;
            result = minFunc(result, caseA_eggBroken + testNum);
        } else {
        	// 如果鸡蛋没有碎, 表示F层应该在mid层的上面???
            low = mid + 1;
            result = minFunc(result, caseB_eggOk + testNum);
        }
    }

    // 备忘录用起来
    arr[eggNum][totalFloor] = result;
    return result;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#include "lib_prot_pd.h" #include "mod_input_output.h" #include "mod_display.h" #include "mod_queue.h" #include "app_typec_deal.h" #include "app_init.h" #include "lib_multi_protocol.h" uint8_t typeca_ufp= 0; void typeca_hard_reset_cb(void) { static uint8_t b_typec_hard_reset_lock= RESET; if(h_pd.output.b_source_read_hard|| \ h_pd.output.b_sink_read_hard) { if(b_typec_hard_reset_lock== RESET) { b_typec_hard_reset_lock= SET; mod_queue_send(PLUG_CA_RST_P); } }else { if(b_typec_hard_reset_lock) { b_typec_hard_reset_lock= RESET; mod_queue_send(PLUG_CA_RST_N); } } } void typecb_hard_reset_cb(void) { static uint8_t b_typec_hard_reset_lock= RESET; if(h_pd.output.b_source_read_hard|| \ h_pd.output.b_sink_read_hard) { if(b_typec_hard_reset_lock== RESET) { b_typec_hard_reset_lock= SET; mod_queue_send(PLUG_CB_RST_P); } }else { if(b_typec_hard_reset_lock) { b_typec_hard_reset_lock= RESET; mod_queue_send(PLUG_CB_RST_N); } } } void typeca_pr_swap_cb(void) { static uint8_t b_pd_power_role_swap_lock; if(h_pd.output.b_pr_swap_en) { if(h_pd.output.b_source_read_pr_swap) { b_pd_power_role_swap_lock= SET; /*用户代码*/ //关 VBus }else if(h_pd.output.b_sink_read_pr_swap) { /*用户代码*/ //DC-DC 进入放电状态,升压,打开 VBus } }else { b_pd_power_role_swap_lock= RESET; } } void typecb_pr_swap_cb(void) { static uint8_t b_pd_power_role_swap_lock; if(h_pd.output.b_pr_swap_en) { if(h_pd.output.b_source_read_pr_swap) { b_pd_power_role_swap_lock= SET; /*用户代码*/ //关 VBus }else if(h_pd.output.b_sink_read_pr_swap) { /*用户代码*/ //DC-DC 进入放电状态,升压,打开 VBus } }else { b_pd_power_role_swap_lock= RESET; } } bool typeca_vbus_exist_cb(void) { //判断一下0.8V VBus电压 return mod_io_typeca_acin(); } bool typecb_vbus_exist_cb(void) { //判断一下0.8V VBus电压 return mod_io_typecb_acin(); } void typeca_attached_src_cb(void) { /** * !!! * 兼容性测试需要 */ mod_io_a1d_out(); mod_queue_send(PLUG_CAD_IN); } void typecb_attached_src_cb(void) { mod_io_a1d_out(); mod_queue_send(PLUG_CBD_IN); } void typeca_attached_snk_cb(void) { mod_queue_send(PLUG_CAC_IN); typeca_ufp = 1; } void typecb_attached_snk_cb(void) { mod_queue_send(PLUG_CBC_IN); typeca_ufp = 1; } void typeca_unattached_cb(void) { mod_queue_send(PLUG_CAX_OUT); typeca_ufp = 0; } void typecb_unattached_cb(void) { mod_queue_send(PLUG_CBX_OUT); }
最新发布
06-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值