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/