题目描述
给你 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)$
- 状态 $f(i,j)$ 表示使用 $i$ 个鸡蛋,最大楼层为 $j$ 时,所需要的最少移动次数。
- 显然初始状态 $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$ 层碎掉和没有碎掉。
- 如果直接枚举 $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$ 作比较,取最小。
- 至此,$s$ 可以二分解决;总时间复杂度为 $O(KN\log N)$。
- 但进一步可以发现,$s$ 会随着 $j$ 的增加而递增,即最优决策点 $s’$ 是随着 $j$ 单调递增的。因为对于固定的 $s$,$f(i, j - s)$ 会随着 $j$ 而增加,这就会造成 3 中的最优决策点也会向后移动。所以,我们只需要在每一次移动 $j$ 后,继续从上一次的 $s’$ 向后寻找最优决策点即可。
- 最终答案为 $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)$
- 状态 $f(i, j)$ 表示进行 $i$ 次移动,有 $j$ 个鸡蛋,最多可以检查的楼层高度是多少。
- 初始状态 $f(1, 0) = 0$,$f(1, j) = 1, j \ge 1$。
- 先给出转移方程,$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$ 层。
- 如果测试时鸡蛋碎掉了,则我们可以通过 $i-1$ 次移动和 $j-1$ 个鸡蛋来找到最高不会碎掉楼层,因为楼层不会超过 $n_1$ 了;如果鸡蛋没有碎掉,则在此基础上,我们可以使用 $i-1$ 次移动和 $j$ 个鸡蛋,再继续向上检查 $n_2$ 层,故只要是答案在 $[0, n_1+n_2+1]$ 范围内,都可以通过 $i$ 步和 $j$ 个鸡蛋来找到。
- 返回最小的 $m$ 满足,$f(m, k) \ge n$。
- 这里,状态的第一维可以省略,更新时只需要倒序更新即可。
时间复杂度
- 最多进行 $\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;
}
};
你的方程$\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 ) $ 岂不是得到的错误的值?
我想明白了, 当整个区间是$ [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 $ 次就能测出答案.
转移方程中的min的右侧上下的小的下标和上标指的是什么语义呀
从 s=1 枚举到 j,所有情况下的最小值
学神!