鸡蛋掉落

887. 鸡蛋掉落

题目描述

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F ,满足 \(0<= F <= N\) 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 \(1 <= X <= N\))。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

说明

\(1 <= K <= 100\)

\(1 <= N <= 10000\)

示例

输入: K = 2, N = 6

输出: 3

输入:K = 3, N = 14

输出:4

思路

我们可以考虑使用动态规划来做这道题,状态可以表示成 (K, N),其中 K为鸡蛋数,N 为楼层数。当我们从第 X 楼扔鸡蛋的时候:

  • 如果鸡蛋不碎,那么状态变成 (K, N-X),即我们鸡蛋的数目不变,但答案只可能在上方的 N−X 层楼了。也就是说,我们把原问题缩小成了一个规模为 (K, N-X) 的子问题;

  • 如果鸡蛋碎了,那么状态变成 (K-1, X-1),即我们少了一个鸡蛋,但我们知道答案只可能在第 X 楼下方的 X-1层楼中了。也就是说,我们把原问题缩小成了一个规模为 (K-1, X-1)的子问题。

这样一来,我们定义 dp(K, N)为在状态 (K, N)下最少需要的步数。根据以上分析我们可以列出状态转移方程

\[dp(K,N)=1+min(max(dp(K−1,X−1),dp(K,N−X))) \]

代码

//work it right
class Solution {
public:
    int superEggDrop(int K, int N) {
        vector<vector<int>> dp(K+1,vector<int>(N+1,0));
        int eggNUM=K;
        int floorNUM=N;
        for(int i=1;i<=floorNUM;++i)
            dp[1][i]=i;
        for (int i = 1; i <= eggNUM; ++i)
            dp[i][1]=1;
        for(int i=2;i<=eggNUM;i++)
        {
            for(int j=2;j<=floorNUM;j++)
            {
                dp[i][j]=j;
                for(int x=1;x<j;++x)
                    dp[i][j]=min(dp[i][j],max(dp[i][x-1],dp[i-1][j-x])+1);
            }
        }
        return dp[K][N];
    }
};

时间复杂度\(\mathcal{O}(KN^2)\)

优化:

\(dp[i][x-1]\)\(dp[i-1][j-x]\),前者关于\(x\)单调递增,后者关于\(x\)单调递减,两者最大值的最小值应该在两条直线交点上或下。可以采用二分搜索不断搜索这个值

  • \(dp[i][mid-1]<dp[i-1][j-mid]\),说明增加的不够多,继续增
  • \(dp[i][mid-1]<dp[i-1][j-mid]\),增加的太多了,减下去
  • \(dp[i][mid-1]==dp[i-1][j-mid]\),皆大欢喜。没有也不要紧
//work it fast
class Solution
{
public:
    int superEggDrop(int K, int N)
    {
        vector<vector<int>> dp(K + 1, vector<int>(N + 1, 0));
        int eggNUM = K;
        int floorNUM = N;
        for (int i = 0; i <= floorNUM; ++i)
            dp[1][i] = i;
        for (int i = 1; i <= eggNUM; ++i)
            dp[i][1] = 1;
        for (int i = 2; i <= eggNUM; i++)
        {
            for (int j = 2; j <= floorNUM; j++)
            {
                dp[i][j] = j;
                int left=1,right=j-1,mid;
                while(left<right)
                {
                    mid=(left+right)/2;
                    if(dp[i][mid-1]<dp[i-1][j-mid])
                        left=mid+1;
                    else if(dp[i][mid-1]>dp[i-1][j-mid])
                        right=mid-1;
                    else
                        left=right=mid;
                }
                dp[i][j]=1+min(max(dp[i][left-1],dp[i-1][j-left]),max(dp[i][right-1],dp[i-1][j-right]));
            }
        }
        return dp[K][N];
    }
};

时间复杂度:\(\mathcal{O}(KNlog_2N)\)

posted @ 2020-09-10 10:24  周颖  阅读(57)  评论(0)    收藏  举报