AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

LeetCode 887. 鸡蛋掉落    原题链接    困难

作者: 作者的头像   wzc1995 ,  2018-10-26 11:51:03 ,  所有人可见 ,  阅读 5029


22


8

题目描述

给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。

已知存在楼层 f,满足 0 <= f <= n,任何从 高于 f 的楼层落下的鸡蛋都 会碎,从 f 楼层或比它低 的楼层落下的鸡蛋都 不会碎。

每次操作,你可以取一枚 没有碎 的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。

请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?

样例

输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0。
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1。 
如果它没碎,那么肯定能得出 f = 2。
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。
输入:k = 2, n = 6
输出:3
输入:k = 3, n = 14
输出:4

限制

  • 1 <= k <= 100
  • 1 <= n <= 10^4

算法1

(动态规划) $O(kn)$
  1. 状态 $f(i,j)$ 表示使用 $i$ 个鸡蛋,最大楼层为 $j$ 时,所需要的最少移动次数。
  2. 显然初始状态 $f(1, j) = j$,其余为无穷。对于状态 $f(i,j)$,如果枚举上一次测试为楼层 $s$,则可以得到如下转移 $f(i,j)=\min_{s=1}^{j}(\max(f(i - 1, s - 1), f(i, j - s)) + 1)$,分别表示鸡蛋在第 $s$ 层碎掉和没有碎掉。
  3. 如果直接枚举 $s$,则总时间复杂度为 $O(KN^2)$,无法通过。考虑 $f(i - 1, s - 1)$ 和 $f(i, j - s)$ 的大小关系,可以发现,前者随着 $s$ 单调递增,后者单调递减,且每次变化的值最多为 1。所以,如果存在 $s’$ 使得 $f(i - 1, s - 1) = f(i, j - s)$,则此时 $s’$ 就是最优的;否则取二者最相近的两个 $s$ 作比较,取最小。
  4. 至此,$s$ 可以二分解决;总时间复杂度为 $O(KN\log N)$。
  5. 但进一步可以发现,$s$ 会随着 $j$ 的增加而递增,即最优决策点 $s’$ 是随着 $j$ 单调递增的。因为对于固定的 $s$,$f(i, j - s)$ 会随着 $j$ 而增加,这就会造成 3 中的最优决策点也会向后移动。所以,我们只需要在每一次移动 $j$ 后,继续从上一次的 $s’$ 向后寻找最优决策点即可。
  6. 最终答案为 $f(k,n)$。

时间复杂度

  • 状态数为 $O(kn)$,对于每个 $i$,寻找最优决策的均摊总时间为 $O(n)$,故总时间复杂度为 $O(kn)$。

C++ 代码

class Solution {
public:
    int superEggDrop(int k, int n) {
        vector<vector<int>> f(k + 1, vector<int>(n + 1, INT_MAX));
        for (int i = 0; i <= n; i++)
            f[1][i] = i;

        for (int i = 2; i <= k; i++) {
            int s = 1;
            f[i][0] = 0;
            for (int j = 1; j <= n; j++) {
                while (s < j && max(f[i - 1][s - 1], f[i][j - s]) >= 
                max(f[i - 1][s], f[i][j - s - 1]))
                    s++;

                f[i][j] = min(f[i][j], max(f[i - 1][s - 1], f[i][j - s]) + 1);
            }
        }

        return f[k][n];
    }
};

算法2

(动态规划) $O(k \log n)$
  1. 状态 $f(i, j)$ 表示进行 $i$ 次移动,有 $j$ 个鸡蛋,最多可以检查的楼层高度是多少。
  2. 初始状态 $f(1, 0) = 0$,$f(1, j) = 1, j \ge 1$。
  3. 先给出转移方程,$f(i, j) = f(i - 1, j - 1) + f(i - 1, j) + 1$。假设 $n_1 = f(i - 1, j - 1), n_2 = f(i - 1, j)$,我们在第 $i$ 次移动时测试第 $n_1 + 1$ 层。
  4. 如果测试时鸡蛋碎掉了,则我们可以通过 $i-1$ 次移动和 $j-1$ 个鸡蛋来找到最高不会碎掉楼层,因为楼层不会超过 $n_1$ 了;如果鸡蛋没有碎掉,则在此基础上,我们可以使用 $i-1$ 次移动和 $j$ 个鸡蛋,再继续向上检查 $n_2$ 层,故只要是答案在 $[0, n_1+n_2+1]$ 范围内,都可以通过 $i$ 步和 $j$ 个鸡蛋来找到。
  5. 返回最小的 $m$ 满足,$f(m, k) \ge n$。
  6. 这里,状态的第一维可以省略,更新时只需要倒序更新即可。

时间复杂度

  • 最多进行 $\log n$ 轮更新,每轮更新需要 $O(k)$ 的时间,故时间复杂度为 $O(k \log n)$。

C++ 代码

class Solution {
public:
    int superEggDrop(int k, int n) {
        vector<int> f(k + 1, 1);

        f[0] = 0;
        int m = 1;
        while (f[k] < n) {
            for (int i = k; i >= 1; i--)
                f[i] = f[i] + f[i - 1] + 1;
            m++;
        }

        return m;
    }
};

5 评论


用户头像
mungo   2022-05-17 18:00      1    踩      回复

你的方程$\mathit f \left ( \mathit i , \mathit j \right ) = \mathit n_1 + \mathit n_2 + 1$ , 若鸡蛋硬度不够,在试第 $\mathit n_1 + 1$ 层时碎了, 那么此时 $\mathit n_2$ 又不等于 0 , $\mathit f \left ( \mathit i , \mathit j \right ) $ 岂不是得到的错误的值?

用户头像
mungo   2022-05-17 19:02      1    踩      回复

我想明白了, 当整个区间是$ [0, \mathit n_1 + \mathit n_2 + 1] $时,我们第一次直接从 $\mathit n_1 + 1$ 层测试 : 如果鸡蛋碎了, 我们可以保证在区间$[0, \mathit n_1]$ 上只需要进行 $ \mathit i - 1$ 次就可以测出答案; 如果鸡蛋没碎, 我们也可以保证在区间$[\mathit n_1 + 1, \mathit n_1 + \mathit n_2 + 1]$ 里只需要进行 $ \mathit i - 1 $ 次就能测出答案.


用户头像
Omega_   2022-02-17 17:59         踩      回复

转移方程中的min的右侧上下的小的下标和上标指的是什么语义呀

用户头像
wzc1995   2022-02-21 16:59         踩      回复

从 s=1 枚举到 j,所有情况下的最小值


用户头像
Omega_   2020-03-04 20:02         踩      回复

学神!


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息