【问题描述】 [887. 鸡蛋掉落]
你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。你的目标是确切地知道 F 的值是多少。无论 F 的初始值如何,你确定 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来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/super-egg-drop
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
【解答思路】
李永乐老师的视频:《复工复产找工作?先来看看这道面试题:双蛋问题》
方法:动态规划
1. 动态规划 -超时
时间复杂度:O(N^3) 空间复杂度:O(N^2)
import java.util.Arrays;public class Solution {public int superEggDrop(int K, int N) {// dp[i][j]:一共有 i 层楼梯的情况下,使用 j 个鸡蛋的最少实验的次数// 注意:// 1、i 表示的是楼层的大小,不是第几层的意思,例如楼层区间 [8, 9, 10] 的大小为 3,这一点是在状态转移的过程中调整的定义// 2、j 表示可以使用的鸡蛋的个数,它是约束条件,我个人习惯放在后面的维度,表示消除后效性的意思// 0 个楼层和 0 个鸡蛋的情况都需要算上去,虽然没有实际的意义,但是作为递推的起点,被其它状态值所参考int[][] dp = new int[N + 1][K + 1];// 由于求的是最小值,因此初始化的时候赋值为一个较大的数,9999 或者 i 都可以for (int i = 0; i <= N; i++) {Arrays.fill(dp[i], i);}// 初始化:填写下标为 0、1 的行和下标为 0、1 的列// 第 0 行:楼层为 0 的时候,不管鸡蛋个数多少,都测试不出鸡蛋的 F 值,故全为 0for (int j = 0; j <= K; j++) {dp[0][j] = 0;}// 第 1 行:楼层为 1 的时候,0 个鸡蛋的时候,扔 0 次,1 个以及 1 个鸡蛋以上只需要扔 1 次dp[1][0] = 0;for (int j = 1; j <= K; j++) {dp[1][j] = 1;}// 第 0 列:鸡蛋个数为 0 的时候,不管楼层为多少,也测试不出鸡蛋的 F 值,故全为 0// 第 1 列:鸡蛋个数为 1 的时候,这是一种极端情况,要试出 F 值,最少次数就等于楼层高度(想想复杂度的定义)for (int i = 0; i <= N; i++) {dp[i][0] = 0;dp[i][1] = i;}// 从第 2 行,第 2 列开始填表for (int i = 2; i <= N; i++) {for (int j = 2; j <= K; j++) {for (int k = 1; k <= i; k++) {// 碎了,就需要往低层继续扔:层数少 1 ,鸡蛋也少 1// 不碎,就需要往高层继续扔:层数是当前层到最高层的距离差,鸡蛋数量不少// 两种情况都做了一次尝试,所以加 1dp[i][j] = Math.min(dp[i][j], Math.max(dp[k - 1][j - 1], dp[i - k][j]) + 1);}}}return dp[N][K];}
}作者:liweiwei1419
链接:https://leetcode-cn.com/problems/super-egg-drop/solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang/
2. 优化
时间复杂度:O(N^2logN) 空间复杂度:O(N^2)
import java.util.Arrays;public class Solution {public int superEggDrop(int K, int N) {// dp[i][j]:一共有 i 层楼梯的情况下,使用 j 个鸡蛋的最少仍的次数int[][] dp = new int[N + 1][K + 1];// 初始化for (int i = 0; i <= N; i++) {Arrays.fill(dp[i], i);}for (int j = 0; j <= K; j++) {dp[0][j] = 0;}dp[1][0] = 0;for (int j = 1; j <= K; j++) {dp[1][j] = 1;}for (int i = 0; i <= N; i++) {dp[i][0] = 0;dp[i][1] = i;}// 开始递推for (int i = 2; i <= N; i++) {for (int j = 2; j <= K; j++) {// 在区间 [1, i] 里确定一个最优值int left = 1;int right = i;while (left < right) {// 找 dp[k - 1][j - 1] <= dp[i - mid][j] 的最大值 kint mid = left + (right - left + 1) / 2;int breakCount = dp[mid - 1][j - 1];int notBreakCount = dp[i - mid][j];if (breakCount > notBreakCount) {// 排除法(减治思想)写对二分见第 35 题,先想什么时候不是解// 严格大于的时候一定不是解,此时 mid 一定不是解// 下一轮搜索区间是 [left, mid - 1]right = mid - 1;} else {// 这个区间一定是上一个区间的反面,即 [mid, right]// 注意这个时候取中间数要上取整,int mid = left + (right - left + 1) / 2;left = mid;}}// left 这个下标就是最优的 k 值,把它代入转移方程 Math.max(dp[k - 1][j - 1], dp[i - k][j]) + 1) 即可dp[i][j] = Math.max(dp[left - 1][j - 1], dp[i - left][j]) + 1;}}return dp[N][K];}
}作者:liweiwei1419
链接:https://leetcode-cn.com/problems/super-egg-drop/solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang/
【总结】
1.「动态规划」的两个思考方向:
1.自顶向下求解,称之为「记忆化递归」:初学的时候,建议先写「记忆化递归」的代码,然后把代码改成「自底向上」的「递推」求解;
2.自底向上求解,称之为「递推」或者就叫「动态规划」:在基础的「动态规划」问题里,绝大多数都可以从这个角度入手,做多了以后建议先从这个角度先思考,实在难以解决再考虑「记忆化递归」。
2. 「动态规划」五步走
第 1 步:定义状态
第 2 步:推导状态转移方程
第 3 步:考虑初始化
第 4 步:考虑输出
第 5 步:思考状态压缩
3.二分查找优化
参考链接:https://leetcode-cn.com/problems/super-egg-drop/solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang/.