算法面试难题-1-鸡蛋掉落问题
作者:互联网
问题描述
方法1——动态规划(超时)
class Solution {
public:
int superEggDrop(int K, int N) {
const int INF=0x3f3f3f3f;
//dp[i][j]:一共有i层楼梯的情况下,使用j个鸡蛋的最少实验的次数
vector<vector<int>>dp(N+1,vector<int>(K+1,0));
//初始化
for(int i=0;i<=N;i++){
for(int j=0;j<=K;j++){
dp[i][j]=INF;
}
}
//第0行:楼层为0的时候,不管鸡蛋个数多少,都测试不出鸡蛋的F值,故全为0
for(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
//不碎,就需要往高楼继续仍:层数是当前层到最高层的距离差,鸡蛋数量不少
//两种情况都做了一次尝试,所以加1
dp[i][j]=min(dp[i][j],max(dp[k-1][j-1],dp[i-k][j])+1);
}
}
}
return dp[N][K];
}
};
复杂度分析:
- 时间复杂度:O(N2K),三层for循环,每层循环都是线性的。
- 空间复杂度:O(NK),表格大小。
方法2——动态规划+二分
这里需要盯着[状态转移方程]使劲看:
dp[i][j]=min(max(dp[k-1][j-1],dp[i-k][j])+1);
[状态转移方程]里最外层的变量是k,它枚举了扔下鸡蛋的楼层的高度,这里它是自变量,将其余的i和j视为常数:
- dp[k-1][j-1]:根据语义,k增大的时候,楼层大小越大,它的值就越大;
- dp[i-k][j]:根据语义,k增大的时候,楼层大小越小,它的值就越小。
可以得出一个是单调不减的(dp[k-1][j-1],下面红点),一个是单调不增的(dp[i-k][j],下面绿星),并且它们的值都是整数。
我们使用了一组数据,制作成图标(每次取数据都取最后一行最后一列的那个单元格计算的数据)。
情况1:最低点只有1个点
情况2:最低点是若干个重合的点
情况3:最低点不重合,但是两边的值一样
从图上可以看出:二者的较大值的最小点在它们交汇的地方。那么有没有可能不交汇,当前有可能(上面第3张图),二者较大值的最小者一定出现在画成曲线段交点的两侧,并且二者的差值不会超过1,也就是如果没有重合的点,两边的最大值是一样的(从图上看出来的,没有严格证明),因此取左侧和右侧两点中的一点都可以,不失一般性,可以取左边的那个点的k。
也就是找到使得dp[i-k][j]<=dp[k-i][j-1]最大的那个k值即可。这里使用二分查找算法。关键在于dp[i-k][j]>dp[k-i][j-1]的时候,k一定不少我们要找的,根据这一点写出二分的代码。
class Solution {
public:
int superEggDrop(int K, int N) {
const int INF=0x3f3f3f3f;
//dp[i][j]:一共有i层楼梯的情况下,使用j个鸡蛋的最少实验的次数
vector<vector<int>>dp(N+1,vector<int>(K+1,0));
//初始化
for(int i=0;i<=N;i++){
for(int j=0;j<=K;j++){
dp[i][j]=INF;
}
}
//第0行:楼层为0的时候,不管鸡蛋个数多少,都测试不出鸡蛋的F值,故全为0
for(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++){
//在区间[1,i]里确定一个最优值
int left=1;
int right=i;
while(left<right){
//找dp[k-1][j-1]<=dp[i-mid][j]的最大值k
int mid=left+(right-left+1)/2;
int breadCount=dp[mid-1][j-1];
int noBreakCount=dp[i-mid][j];
if(breadCount>noBreakCount){
//严格大于的时候一定不是解,此时mid一定不是解
//下一轮搜索区间是[left,mid-1]
right=mid-1;
}else {
//这个区间一定是上一个区间的反面,即[mid,right]
//注意这个时候取中间数要上取整,int mid=left+(right-left+1)/2;
left=mid;
}
}
//left这个下标就是最优的k值,把它代入转移方程
dp[i][j]=max(dp[left-1][j-1],dp[i-left][j])+1;
}
}
return dp[N][K];
}
};
复杂度分析:
- 时间复杂度:O(NK \log N)O(NKlogN),其中一层循环变成二分查找,复杂度成为对数;
- 空间复杂度:O(NK)O(NK),表格的大小。
标签:掉落,int,鸡蛋,面试,算法,楼层,复杂度,dp,left 来源: https://blog.csdn.net/qq_41476257/article/details/114399340