题目
假设面前有一栋N层的高楼, 然后给你K个鸡蛋,现在请你求出 在鸡蛋恰好没碎的时候的最坏情况下,至少要扔几次鸡蛋,才能确定这个楼层F呢?(例子: N = 7, K = 2 ,输出 7)
思路分析
首先我们要理解最坏情况的意思, 每次鸡蛋破碎的情况都发生在搜索区域的穷尽端,而不会因为运气好而发生在搜索区域的前端,我们就称该情况为最坏情况,要采用'动态规划' 的做法我们就要可考虑状态了,状态的设置我们只要考虑N和K的变化就可以了,而选择上我们该怎么考虑呢?
我们直接上代码:
int maxOperationCount(int eggSize, int Count)//count代表层数并非代表总高度
{
int i, res = M;
if(eggSize == 1) return Count;//只剩最后一个鸡蛋,我们就直接线性遍历,所以最坏的情况为全部遍历一遍。
if(Count == 0) return 0;//高度为0时,就不需要再向下执行了。
for(i = 1; i <= Count; i++){//为了保证找到对应的步数,我们必须遍历开始时不同的扔鸡蛋的位置
res = Min(res,
Max(
maxOperationCount(eggSize - 1, i - 1),//碎了,鸡蛋数减一,所以我们只要求楼层在自己的基础上高度减一的层数对应的测试步数。
maxOperationCount(eggSize, Count - i)//没碎,楼层数为此层之上,所以我们只要求i到Count的层数对应的测试步数。
)
) + 1;//每步要加一;
}
return res;
}
int Max(int a, int b)
{
return (a >= b) ? a : b;
}
int Min(int a,int b)
{
return (a >= b) ? b : a;
}
代码对应的时间复杂度为O(N^2)。
为了能够列出所有的情况,我们需要进行一个循环,将当前的楼层数全部循环一遍,找出每层鸡蛋会碎的情况和鸡蛋不会碎的情况,因此选择就分为 鸡蛋会碎和鸡蛋不会碎 ,因为要求最坏情况,所以我们要在每层的最多步数中找最小的值,最终就可以求出答案。
代码优化方案
代码写完了,我们接下来来谈谈代码的优化方案。
1.’备忘录‘方案
因为只存在变量 eggsize,Count,所以我们可以创建一个二维数组x轴为eggsize,y轴为Count,存放与其对应的数据,从而减少重复问题的计算时。由于代码修改幅度比较小,这里就不展示对应的代码了。
如有疑问可以阅读下面链接对应的文章。
https://blog.csdn.net/Ostkakah/article/details/118160844?spm=1001.2014.3001.5501
2. '二分查找'方案
由起始代码我们可以构建出maxOperationCount(eggSize - 1, i - 1)和maxOperationCount(eggSize, Count - i)对应的函数关系图:
此图仅为一个大概的情况,我们从图中可以看出两函数相交时,就是最终测试步数最小的时候。所以我们可以尝试通过二分法来确定出相交时的位置,这样一来看,我们需要修改的代码其实也不多,就是将普通的线性遍历优化为二分查找遍历。
对应代码如下:
int superEggDrop(int egg, int count)
{
if(egg == 1){
return count;
}
if(count == 0){
return 0;
}
int res = 999999;
int low = 0, high = count;
while(low <= high){
mid = (low + high) / 2;
int breakn = maxOperationCount(egg - 1, mid - 1);//该层对应的碎的测试次数;
int not_breakn = maxOperationCount(egg, count - mid);//该层对应的未碎的测试次数;
if(breakn > not_breakn){
/*当碎的测试次数大于未碎的次数时,我们可以确定最低点的层数小于 mid*/
high = mid - 1;
res = Min(res, breakn + 1);
}else{
/*当碎的测试次数小于或等于未碎的次测试次数时,我们可以确定最低点的层数大于mid*/
low = mid + 1;
res = Max(res, not_breakn + 1);
}
}
return res;
}
代码对应的时间复杂度为O(NlogN)。
我们通过low,high来确定交点的位置,并用变量来记录当前层数碎和不碎的测试步数,比较两变量的大小,当breakn > not_breakn时说明交点在mid的左侧,反之就在其右侧或就在mid,最终就可以求出res的值。
3.建立关系二维数组
我们尝试使用鸡蛋数和总测试步数构建的关系二维数组, 二维数组的值代表层数,从左向右每次向下遍历。(不一定必须从左向右每次向下遍历,也可以从上到下每次向右遍历)
对应代码如下:
int superEggDrop(int K, int N)
{
static int dp[K + 1][N + 1];//不存在鸡蛋个数为0的情况且测试步数可能等于N
int m = 0;
while(dp[K][m] < N){//循环找到dp[K][m]=N的位置
m++;
int i;
for(i = 1; i <= K; i++){//计算鸡蛋个数不同时的测试步数
//dp[i - 1][m - 1]为碎的情况, dp[i][m - 1]为碎的情况, 最终值要 + 1是因为中间层两者都没包括。
dp[i][m] = dp[i - 1][m - 1] + dp[i][m - 1] + 1;//m - 1 是因为碎和没碎两种情况都没包括测试时的步数
}
}
return m;
}
该代码的时间复杂度为O(KN)。
通过循环在dp[K][1]~dp[K][N]中按顺序查找出第一个值为N的项,该项就是在最坏情况下的最少测试步数。
将该思路进一步优化,将该二维数组压缩为一维数组降低其空间复杂度的代码为下:
int superEggDrop(int K, int N)
{
static int dp[N + 1];
int i, j, data;
for(i = 1; i <= K; i++){//先确定最后一行的数值
data = 0;//用来记录'左上'对应的值
for(j = 1; j <= N; j++){
int temp = dp[j];
dp[j] = dp[j - 1] + data + 1;//dp[j - 1]为'左'值
data = temp;
}
}
for(i = 1; i <= N; i++){//通过循环确定最终值
if(dp[i] == N) return i;
}
return ;
}
将其空间复杂度降低为O(N)。
最终通过压缩写出的代码的时间复杂度为O(KN) 空间复杂度为O(N),我认为能做出这种解法已经很好了,我们就不再优化了。