leetcode887 鸡蛋掉落

887 super-egg-drop

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

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

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

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

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

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

思路1:动态规划
1)如果不考虑有几个鸡蛋,最优解法是二分搜索,相当于猜数字游戏
2)如果鸡蛋只有一个,那肯定是从N层往下尝试,到k层碎掉,F=k+1(根据题意x<F,则鸡蛋碎掉,在F-1层会碎),最差的情况则是F=0,那么就要尝试N次(到第一层还是没碎则F=0)
3)如果只有一层,则只要尝试一次(在第1层碎了,F=0;第一层没碎,F=1)
4)普通情况,我们用dp(k, n)表示有k个鸡蛋,n层楼,需要移动的次数。我们选择在test层进行测试,如果鸡蛋碎了,那么F<test,只需要尝试小于test的层,即dp(k-1, test-1);如果没碎,那么F>test,只要尝试高于test的层,即dp(k, n-test)。那么到底该选择哪一个呢?选择接下来会带来更差情况的那种可能,即两个值中的较大值
5)我们选择尝试每一层,选取最小值

class Solution {
public:
    int superEggDrop(int K, int N) {
        // [鸡蛋数目][楼层数目]
        vector<vector<int>> dp(K+1, vector<int>(N+1, 0));
        // base
        for(int n=1;n<=N;n++)
        {
            dp[1][n] = n;
        }
        for(int k=1;k<=K;k++)
        {
            dp[k][1] = 1;
        }
        // state transfer
        for(int k=2;k<=K;k++)// k个鸡蛋
        {
            for(int n=2;n<=N;n++) // n层楼
            {
                // 碎了:dp[k-1][test-1]
                // 没碎:dp[k][n-test]
                dp[k][n] = max(dp[k-1][0], dp[k][n-1]); //test = 1
                for(int test = 2;test<=n;test++)
                    dp[k][n] = min(dp[k][n],  
                                   max(dp[k-1][test-1], // 碎了
                                       dp[k][n-test])); // 没碎
                dp[k][n]+=1;
            }
        }
        return dp[K][N];
    }
};

思路2
上面的方法会超时,3个for循环的时间复杂度为O(KN^2),需要进行优化。
在循环内查找最小值为线性查找,可以优化为二分查找。
dp[k-1][test-1]:k和n是固定的,随着test的增大而增大
dp[k][n-test]:k和n是固定的,随着test的增大而减小
则相当于求一个线性递增函数和一个线性递减函数的相交值(因为test是离散的,那么久转化为了求离相交值最近的两个test值,这两个值中更小的那个test)(详见leetcode官方题解)

class Solution {
public:
    int superEggDrop(int K, int N) {
        // [鸡蛋数目][楼层数目]
        vector<vector<int>> dp(K+1, vector<int>(N+1, 0));
        // base
        for(int n=1;n<=N;n++)
        {
            dp[1][n] = n;
        }
        // state transfer
        for(int k=2;k<=K;k++)// k个鸡蛋
        {
            for(int n=1;n<=N;n++) // n层楼
            {
                // 碎了:dp[k-1][test-1]
                // 没碎:dp[k][n-test]
                int l = 1, r = n, mid;
                while(l+1<r)
                {
                    mid = (l+r) >> 1;
                    if(dp[k-1][mid-1] < dp[k][n-mid]) l = mid+1;
                    else r = mid;
                }
			    dp[k][n] = 1 + min(max(dp[k - 1][l - 1], dp[k][n - l]),
				    max(dp[k - 1][r - 1], dp[k][n - r]));
            }
        }
        return dp[K][N];
    }
};

思路3
除了二分查找,可以分析到
dp[k-1][test-1]:k和n是固定的,随着test的增大而增大;且与n无关(黑色线)
dp[k][n-test]:k和n是固定的,随着test的增大而减小;随着n的增大而增大(红色线)
所以交点一定是递增的
在这里插入图片描述

class Solution {
public:
    int superEggDrop(int K, int N) {
        // [鸡蛋数目][楼层数目]
        vector<vector<int>> dp(K+1, vector<int>(N+1, 0));
        // base
        for(int n=1;n<=N;n++)
        {
            dp[1][n] = n;
        }
        // state transfer
        for(int k=2;k<=K;k++)// k个鸡蛋
        {		
            int test = 1;
            for(int n=1;n<=N;n++) // n层楼
            {
                // 碎了:dp[k-1][test-1]
                // 没碎:dp[k][n-test]
                while (test < n && 
                    max(dp[k - 1][test - 1], dp[k][n - test]) > max(dp[k - 1][test], dp[k][n - test - 1]))
                {
                    test++;
                }
                dp[k][n] = 1 + max(dp[k - 1][test - 1], dp[k][n - test]);
            }
        }
        return dp[K][N];
    }
};

思路4
逆向思考,这个思路真的很赞,然而想不到(参见官方题解)
假设我们可以移动T次,有K个鸡蛋。随机选择一层进行测试,如果碎了,那么我么测试下面还有多少层,即f(T-1, K-1);如果没有碎,那么可以测试当前层上面还有几层,即f(T-1, K),加上当前这一层,即为移动T次,有K个鸡蛋能够测试到的所有层。
f(T, K) = f(T-1, K) + f(T-1, K-1) + 1
=> f(K) = f(K) + f(K-1) + 1

class Solution {
public:
    int superEggDrop(int K, int N) {
        vector<int> f(K + 1, 1);
        f[0] = 0;
        int test = 1;
        while (f[K] < N) {
            for (int i = K; i >= 1; i--)    // test次可以到达的最高层数,因为依赖于前一项的上一个状态,所以需要从后往前计算
                f[i] = f[i] + f[i - 1] + 1;
            test++;
        }
        return test;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值