鸡蛋掉落问题解析

问题定义

原始题目来源于LeetCode https://leetcode-cn.com/problems/super-egg-drop/comments/

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

问题解析

第一反应,二分法。但是鸡蛋数量是有限的。比如K=2,N=6的情况。第一次先扔3楼,3楼如果没碎,则在4-6楼中继续试验;3楼碎了的话,则只能从1楼开始进行,最多次数3。
不过,不适合用于鸡蛋数量少,楼层高的情况。比如K=2,N=50的情况。第一次扔25楼,如果碎了,就必须从1楼还是扔。则最多有25次。如果第一次扔10楼,不碎的话扔20楼,一直到40楼,这样最多的次数为4+10=14次。可见,直接二分的方法不通用。按照几等分的方法也不通用。

第二反应,动态规划。和背包问题有些像,也是基于上一个条件来得出最优解。本题的关键点就转换为找到状态转移方程,以及结束条件。

状态转移方程

使用 dp(K,N) 表示状态转移,表示在有 K 个鸡蛋,N楼时候需要扔鸡蛋的次数。如果在第 i 层扔鸡蛋。

  1. 鸡蛋碎了,在 dp(K-1, i -1) 中找最大值。即现在剩余鸡蛋数目 -1 ,查找 i 层以下的楼层
  2. 鸡蛋没碎,在 dp(K , N - i) 中找最大值。即现在剩余鸡蛋数不变,查找 i ~ N层之间的楼层,即 i+1, i+2, 到 N,总楼层数为 N - i
  3. 于是可以得到状态转移方程如下所示:
    dp(k,N) = max(dp(K -1, i-1), dp(K, N - i) + 1 );
结束条件

状态的终止条件:

  1. 只有1个鸡蛋了,还有多少层没测完就还需要扔多少次
  2. 楼层全部测完了,不管还剩几个鸡蛋都结束
  3. 于是可以得出结束条件有两个
    if (N ==0) {
    dp(k,N) = 0;
    } else if (K == 1) {
    dp(k,N) = N;
    }
    这种做法有点像数学归纳法的证明方式。


    egg_drop.jpg
时间复杂度

由于不知道起始扔鸡蛋的位置,所以在设置起始位置时候,需要遍历来进行查找。
所以 动态规划的时间复杂度 = N * dp(K,N),其中 dp 为一个循环,即O(N),最后得到的复杂度为 O(N^2)。

代码

egg_drop.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define min(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x < _y ? _x : _y; })
#define max(x,y) ({ typeof(x) _x = (x); typeof(y) _y = (y); (void) (&_x == &_y); _x > _y ? _x : _y; })

/**
 * dp - 状态转移函数
 * @K: 鸡蛋个数
 * @N: 总共楼层
 *
 * return: 需要扔鸡蛋次数
 */
int dp(int K, int N)
{
    int i, ret, cnt;
    ret = 0;

    /* 最开始扔鸡蛋位置不确定,需要遍历 */
    for (i = 1; i <= N; i++) {
        /**
         * dp(K - 1, i - 1) 为鸡蛋碎了后,用K-1个鸡蛋测i-1层楼
         * dp(K, N - i) 为鸡蛋没碎后,用K个鸡蛋测(i - 1)层楼
         * +1 表示已经扔了一次,次数++
         */
        cnt = max(dp(K - 1, i - 1), dp(K, N - i)) + 1;  /* 最坏情况下的次数 */
        if (i == 1) {
            ret = cnt;
        } else {
            ret = min(ret, cnt);                            /* 查找不同楼层丢,找到丢次数最少的楼层,即找到最少次数 */
        }
    }

    return ret;
}

/**
 * superEggDrop - 获取最小扔鸡蛋次数
 * @K: 鸡蛋个数
 * @N: 总共楼层
 *
 * return: 需要扔鸡蛋次数
 */
int superEggDrop(int K, int N)
{
    int i, ret, cnt;

    ret = 0;
    if (N == 0) {    /* 到最低层,不需要再扔鸡蛋了 */
        return 0;
    } else if (K == 1) {    /* 鸡蛋只有一个,有几层最多扔几次鸡蛋 */
        return N;
    }

    /* 最开始扔鸡蛋位置不确定,需要遍历 */
    for (i = 1; i <= N; i++) {
        /* 第一次从i层丢下后,需要的次数 */
        cnt = max(dp(K, N - i), dp(K - 1, i - 1)) + 1;
        if (i == 1) {
            ret = cnt;
        } else {
            ret = min(ret, cnt);
        }
    }

    return ret;
}

int main (int argc, char *argv[])
{
    int K, N;
    int ret;

    /* 打桩验证 */
    K = 3;
    N = 14;

    ret = superEggDrop(K, N);
    printf("superEggDrop ret:%d\n", ret);

    return ret;
}

Makefile:

CC = gcc
CFLAGS = -g 
LIBS = -lrt
ELF = egg_drop

SRC1 = egg_drop.c
OBJ1 = $(SRC1:%.c=%.o)

all:    ${ELF}

egg_drop: $(OBJ1)
    ${CC} ${CFLAGS} -o $@ $^ ${LIBS}

$(OBJ1): %.o: %.c 
    ${CC} ${CFLAGS} -c -o $@ $<

clean:
    rm -f ${ELF} *.o *.d

执行效果
./egg_drop
superEggDrop ret:4

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容