鸡蛋掉落

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];

    }

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值