LeetCode算法网站算法题
https://leetcode-cn.com/problems/super-egg-drop/
地位:经典的动态规划算法题
鸡蛋数量的理解:
如果只有一个鸡蛋:那么只能一个一个的往上尝试,因为碎了就不能使用了
如果有无数个鸡蛋,那么就可以用二分法,直到分出来的长度小于等于1就找到分界楼层了,如:N层楼就是2^M>=N,M是最少尝试次数。
有限K个鸡蛋,我们就可以用K-1来缩小查找范围,最后一个鸡蛋逐层去尝试。
所以鸡蛋的数量限制了可尝试的次数
如果有两个鸡蛋的话,思路就是第一个每蛋尝试一次,确定范围后,第二个蛋的尝试次数就缩小一点。
x + (x-1) + (x-2) + ... + 1 = N由此我们想到了逆转的思维——由次数构建模型
为什么第一次尝试的层数是X呢?
我们逆转一下思路,假设最少的尝试次数为x,那么我们第一次应该从第几层开始扔,第x层,为什么呢?我们如果从第x-1层开始扔,碎了的话要重新从第一层开始扔,最多需要x-1次;如果从第x+1层开始扔,碎了的话需要x+1次,都与假设不符。
如果第一次扔鸡蛋在第x层,没有碎,那么问题就演化成两个鸡蛋在100-x层楼下往下扔,最少的尝试次数不超过x-1,那么第二次就是在x+x-1层向下扔,以此类推。
正向的思考:
假设在第k层扔鸡蛋,
如果鸡蛋碎了——那么在这一层楼的上方寻找,楼的高度是N-k,鸡蛋的个数是n
如果鸡蛋,没有碎——那么在这一层的下方寻找,楼的高度是k-1,鸡蛋的个数是n-1
这样就找到的递归式子:
可以采用动态规划的思想,转换成一个函数 F(N,K)F(N,K)F(N,K),代表的就是最优解的最大尝试次数,假设我们第一个鸡蛋扔出的位置在第x层(1≤x≤N),会出现两种情况:
第一个鸡蛋没碎,剩下N-x层楼,剩余K个鸡蛋,函数转变为
F(N−x,K)+1,1≤x≤NF(N-x,K) + 1,1≤x≤NF(N−x,K)+1,1≤x≤N
第一个鸡蛋碎了,剩余x-1层楼,剩余K-1个鸡蛋,函数转变为
F(x−1,K−1)+1,1≤x≤NF(x-1,K-1) + 1,1≤x≤NF(x−1,K−1)+1,1≤x≤N
整体而言,我们要求出的是 N层楼 / K个鸡蛋 条件下,最大尝试次数最小的解,所以这个题目的状态转移方程式如下:
F(N,K)=min(max(F(N−x,K)+1,F(x−1,K−1)+1)),1<=x<=MF(N,K)= min(max(F(N-x,K)+ 1,F(x-1,K-1)+ 1)),1<=x<=MF(N,K)=min(max(F(N−x,K)+1,F(x−1,K−1)+1)),1<=x<=M
max函数的使用是为了一定找到分界楼层,所以取最大值,取次数最多的情况,客观选择最大值;
min函数的使用是为了找到最合适的方案,每一种方案都可以找到最恶劣的情况,主观选择最低成本;
1.简单的递归——模型找次数
2.动态规划
3.优化dp的空间——由于在上述的dp中,发现我们只是用了两行,所以就使用一个数组表示鸡蛋摔碎的情况,另一个表示鸡蛋没有碎的情况
4.换一种dp的想法——上一种的dp是i表示鸡蛋的数量,j表示楼层的高度(是基于知道鸡蛋的数量,楼的高度去求解所有情况的最小方案)
这次的dp思路是——dp的i表示鸡蛋的数量,j表示扔鸡蛋的次数(是基于知道鸡蛋的数量,和扔鸡蛋的次数求解所能求出所有情况的最高楼层)
在回顾一下dp[k][j]的含义, dp[k][j] 代表了 [0,NN]这样一个区间
dp[k][j-1]代表[X,NN] - > [0,NN-X] -> dp[k][j-1] = NN - X
dp[k-1][j-1] 代表[0,X-1] -> dp[k-1][j-1] = X - 1
但是dp[k][j] = NN = (X - 1) + (NN - X) + 1 = dp[k][j-1] + dp[k-1][j-1] + 1
理解:
如果第x次操作碎了,那么就在上方,没碎就在下方,而且剩下的操作是不包括本层的,所以要+1,这是一种根据情况反推模型的思路,dp[k-1][j-1]是碎了的情况,dp[k][j-1]是没有碎的情况,为了一定找到分界楼层,所以上下相加。能得到能检测的最高楼层,含要加上第x操作的楼层。
class Solution {
public:
int superEggDrop(int K, int N) {
vector<vector<int>> dp(N + 1, vector<int>(K + 1));//二维vector的固定长度声明
int m = 0;
while (dp[m][K] < N) {
++m;
for (int j = 1; j <= K; ++j) {
dp[m][j] = dp[m - 1][j - 1] + dp[m - 1][j] + 1;
}
}
return m;
}
}
第一次看完李永乐老师的视频写的代码 :
int superEggDrop(int K, int N)
{
int i=0,j=0;
int move_times,f=0;
int**dp;
dp=new int*[K+1];
for(i=0; i<K+1; i++)//一定要注意对0边界的初始化为0,一是为了防止乱码,二是为了逻辑通顺,除非是全局变量,声明时就初始化为0
dp[i]=new int[N+1];
for(i=0; i<K+1; i++)
{
dp[i][0]=0;
}
for(j=0; j<N+1; j++)
{
dp[0][j]=0;
}
for(i=1; i<K+1; i++)//只有一个鸡蛋时,只能一个一个的放置
dp[i][1]=1;
for(j=1; j<N+1; j++)//只有一层楼时,只用尝试一次
dp[1][j]=j;
for(i=2; i<K+1; i++)
for(j=2; j<N+1; j++)
{
move_f=INT_MAX;//记得每次循环前都要初始化为最大,否则上一个数的计算会影响这一次的比较
for(f=1; f<=j; f++)
{
if(move_f>max(dp[i][j-f],dp[i-1][f-1])+1)//记得是f-1,因为不包括已经扔过的那一层
move_f=max(dp[i][j-f],dp[i-1][f-1])+1;
}
dp[i][j]=move_f;
}
return dp[K][N];
}