LeetCode-887.鸡蛋掉落

LeetCode-887.鸡蛋掉落

题目

给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。
已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 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 <= 104

思路1-普通动态规划

普通动态规划(超时):LeetCode博主链接

解1-普通动态规划

普通动态规划(超时):

class Solution {
public:
    int superEggDrop(int k, int n) {
        if (k == 0 || n == 0)
            return 0;
        else if (k == 1 || n == 1)
            return n;

        vector<vector<int>> map(n + 1, vector<int>(k + 1, 0));

        for (int i = 1; i <= k; ++i)
            map[1][i] = 1;
        for (int i = 1; i <= n; ++i)
            map[i][1] = i;

        int nTemp, maxTemp;
        for (int i = 2; i <= n; ++i) {// i 是楼层数
            for (int j = 2; j <= k; ++j) {// j 是鸡蛋数
                if (j >= i) {
                    map[i][j] = map[i][j - 1];
                } else {
                    nTemp = 1;
                    map[i][j] = INT32_MAX;
                    while (nTemp <= i) {// 在当前楼层数、鸡蛋数状态下,逐层遍历扔鸡蛋的最优次数
                        maxTemp = max(map[nTemp - 1][j - 1], map[i - nTemp][j]) + 1;
                        map[i][j] = maxTemp < map[i][j] ? maxTemp : map[i][j];
                        ++nTemp;
                    }
                }
            }
        }
        return map[n][k];
    }
};

思路2-动归+二分

动归+二分:
f[楼层][鸡蛋]是扔鸡蛋的最少次数
在普通动态规划基础上,我们使用二分来加速寻找每种状态的最优解。
例如:我们要找2鸡蛋,100层楼;
在普通的动态规划中,我们遍历到2鸡蛋,50层楼的状态,那么我们需要从第一层开始往上一层层遍历丢鸡蛋,找到2鸡蛋,50层楼的最少次数。
我们从第二层开始扔鸡蛋:f[2][2] = max(f[1][1], f[48][2])
从第三层扔:      f[3][2] = max(f[2][1], f[47][2])
我们会发现,随着楼层的增加,max左边的f[1][1]、f[2][1]鸡蛋数不变,层数在增加,那么最终结果是递增的
max右边的f[48][2]、f[47][1]鸡蛋数不变,层数在减少,那么最终结果是递减的,
所以当max的左边等于右边,max会得到最小的值。
大致图形如下:
在这里插入图片描述
那么我们通过二分得到中间楼层x,在x,max左>max右,说明max最小值在x左边,相等说明max最小值就在x处,否则max最小值在x右边。

解2-动归+二分

动归+二分:

class Solution {
    unordered_map<int, int> memo;

    int dp(int k, int n) {
        if (memo.find(n * 100 + k) == memo.end()) {
            int ans;
            if (n == 0) {
                ans = 0;
            } else if (k == 1) {
                ans = n;
            } else {// 在当前的楼层数、鸡蛋数状态下,使用二分找扔鸡蛋的最优次数
                int lo = 1, hi = n;
                while (lo + 1 < hi) {
                    int x = (lo + hi) / 2;
                    int t1 = dp(k - 1, x - 1);// 因为我们直接找的中间,所以其他的值是未计算过的,需要通过递归计算出所有需要记忆的值
                    int t2 = dp(k, n - x);

                    if (t1 < t2) {
                        lo = x;
                    } else if (t1 > t2) {
                        hi = x;
                    } else {
                        lo = hi = x;
                    }
                }

                ans = 1 + min(max(dp(k - 1, lo - 1), dp(k, n - lo)),
                              max(dp(k - 1, hi - 1), dp(k, n - hi)));
            }

            memo[n * 100 + k] = ans;
        }

        return memo[n * 100 + k];
    }

public:
    int superEggDrop(int k, int n) {
        return dp(k, n);
    }
};

思路3-决策单调性

决策单调性:
f[楼层][鸡蛋]是扔鸡蛋的最少次数
在普通动态规划基础上,我们使用决策单调性来加速寻找每种状态的最优解。
例如:我们要找2鸡蛋,100层楼;
在普通的动态规划中,我们遍历到2鸡蛋,50层楼的状态,那么我们需要从第一层开始往上一层层遍历丢鸡蛋,找到2鸡蛋,50层楼的最少次数。
我们从第二层开始扔鸡蛋:f[2][2] = max(f[1][1], f[48][2])
从第三层扔:      f[3][2] = max(f[2][1], f[47][2])

我们再来看一下,下一个状态2鸡蛋,51层楼的状态
我们从第二层开始扔鸡蛋:f[2][2] = max(f[1][1], f[49][2])
从第三层扔:      f[3][2] = max(f[2][1], f[48][2])
我们来比对一下这两个状态,我们会发现,max左边是不变的,但是max的右边却向右平移,根据我们之前的图,那么我们所得到的max最小值会在上一个状态的右边,所以我们保留每一楼层的max最小值位置,下一次直接在其右侧开始寻找max最小值即可。

解3-决策单调性

决策单调性:

class Solution {
public:
    int superEggDrop(int k, int n) {
        if (k == 0 || n == 0)
            return 0;
        else if (k == 1 || n == 1)
            return n;

        vector<int> dp(n + 1);
        // 至少一个鸡蛋的情况下,最坏情况的最少次数
        for (int i = 0; i <= n; ++i) {
            dp[i] = i;
        }
        // 从两个鸡蛋开始
        for (int j = 2; j <= k; ++j) {// 鸡蛋数
            vector<int> dp2(n + 1);
            int x = 1;
            dp2[0] = 0;
            for (int m = 1; m <= n; ++m) {// 楼层数
                // 在当前的楼层数、鸡蛋数的状态下,利用上一层状态得到的最优次数的楼层数,直接往后遍历(单调性)
                while (x < m && max(dp[x - 1], dp2[m - x]) >= max(dp[x], dp2[m - x - 1])) {
                    x++;
                }
                dp2[m] = 1 + max(dp[x - 1], dp2[m - x]);
            }
            // 将当前鸡蛋数所有楼层的最优解保存,给下一鸡蛋数状态提供比对依据(随着鸡蛋数的增多,我们所需要的最优解会变小直到不变,所以只保留上一次的鸡蛋数状态即可)
            for (int m = 1; m <= n; ++m) {
                dp[m] = dp2[m];
            }
        }
        return dp[n];
    }
};

思路4-转换题目

转换题目,k个鸡蛋,m次机会,可以找出f的最大楼层数大于n,即可得到最小的m:
f[次数][鸡蛋]是可测试最大楼层
例如:我们要找2鸡蛋,100层楼(次机会),可测试最大楼层数;
我们遍历到2鸡蛋,50次状态,
碎了,状态为1鸡蛋,49次
没碎,状态为2鸡蛋,50次
所以f[2][50] = 1 + f[1][49] + f[2][50]
我们只需要找到最少次数的2个鸡蛋的可测试最大楼层大于100即可

解4-转换题目

转换题目:

class Solution {
public:
    int superEggDrop(int k, int n) {
        if (n == 1) {
            return 1;
        }
        vector<vector<int>> f(n + 1, vector<int>(k + 1));
        for (int i = 1; i <= k; ++i) {
            f[1][i] = 1;
        }
        int ans = -1;
        for (int i = 2; i <= n; ++i) {// 扔的次数(扔的次数必定小于或等于楼层数)
            for (int j = 1; j <= k; ++j) {// 鸡蛋数
                f[i][j] = 1 + f[i - 1][j - 1] + f[i - 1][j];
            }
            if (f[i][k] >= n) {
                ans = i;
                break;
            }
        }
        return ans;
    }
};

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值