鸡蛋掉落
- leetcode887. 鸡蛋掉落
- 题目描述
- 暴力递归 + 二分查找
- 代码演示
- 动态规划
- 代码演示
- 动态规划专题
leetcode887. 鸡蛋掉落
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/super-egg-drop
题目描述
给你 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 <= 10000
暴力递归 + 二分查找
这道题要用动态规划解,但题目本身很难直接推出动态规划的状态转移方程。
所以先用暴力递归尝试写出暴力版本。不过用缓存加二分查找优化后,效率还是可以的,
递归时,首先要考虑的就是递归过程中的变量,这题很容易发现变化的是鸡蛋的个数和楼层,选中一个楼层扔鸡蛋,没破的话呢就去上面楼层继续测试,(鸡蛋个数K 不变,楼层变成n - i):
process(n - i,k);
鸡蛋破了的话,要去下面楼层继续测试:鸡蛋个数减一,楼层减一
process(i - 1,k - 1).
用一个图演示,就容易看懂了:
我们先取每个楼层的最差情况,然后选取中所有楼层最差情况下的最好情况,就是需要最少的次数了。
然后再看下为什么可以进行二分查找,首先找到,要进行二分查找,必须要满足具有单调性,首先想这样一个问题,如果鸡蛋个数固定时,楼层越多,需要测试的次数就越多,因此这个递归具有单调性,
process(n - i,k); i 越大,值会越小,
process(i - 1,k - 1). i 越大值也会越大,
如图演示:
其实就是求交点。
代码演示
public int superEggDrop(int k, int n) {
int[][]dp = new int[n + 1][k + 1];
return process(n,k,dp);
}
/**
* 递归加 二分优化
*/
public static int process(int n,int k,int[][]dp){
if(n == 0){
return 0;
}
if(k == 1){
return n;
}
//缓存
if(dp[n][k] != 0){
return dp[n][k];
}
int res = Integer.MAX_VALUE;
int l = 1;
int r = n;
while(l <= r){
int mid = (l + r) / 2;
//没碎
int p1 = process(n - mid,k,dp);
//碎了
int p2 = process(mid - 1,k - 1,dp);
if(p2 > p1){
r = mid - 1;
res = Math.min(res,p2 + 1 ) ;
}else{
l = mid + 1;
res = Math.min(res,p1 + 1);
}
}
dp[n][k] = res;
return res;
}
动态规划
动态规划时,我们换一个思路去做。
就是我们知道鸡蛋个数和面对的楼层时,就知道最小的扔鸡蛋次数,我们最终要的答案就是dp(k,n).
现在我们修改下dp的含义,确定鸡蛋个数和最多允许的扔鸡蛋次数,就能确定楼的最高层数,具体就是:
dp[k][m] = n;
当前有k个鸡蛋,最多可以尝试扔m次。
比如dp[1][7] = 7
表示一个鸡蛋,最多扔七次,这个状态下楼层最高七层,
这样我们可以得出另外一个状态转移方程:
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
如图演示:
dp[k][m - 1]就是上面的楼层数,因为鸡蛋没碎,才可以上去,鸡蛋个数不变,扔鸡蛋次数减一。
dp[k - 1][m - 1],就是楼下的层数,鸡蛋碎了去楼下,k -1,扔鸡蛋的次数 m - 1.
最后加1 ,加上本身楼层。
根据这个状态转移方程就可以写代码了。
代码演示
/**
* 动态规划
*/
public int superEggDrop7(int k, int n) {
int[][]dp = new int[k + 1][n + 1];
int m = 0;
//楼层 == n 时,退出,
while(dp[k][m] < n){
m++;
for(int i = 1; i <= k;i++){
dp[i][m] = dp[i][m - 1] + dp[i - 1][m - 1] + 1;
}
}
//m 扔的次数。
return m;
}
动态规划专题
leetcode1884. 鸡蛋掉落-两枚鸡蛋
leetcode72. 编辑距离
leetcode1049. 最后一块石头的重量 II
leetcode312. 戳气球
leetcode63. 不同路径 II
leetcode62. 不同路径