扔鸡蛋问题

一、地址

二、题目:扔鸡蛋问题

如果你有N颗鸡蛋,和一栋K层高的楼,现在你想知道在哪一层楼之下,鸡蛋不会被摔碎,应该如何用最少的测试次数对于任何答案楼层都能够使问题得到解决。

  • 如果你从某一层楼扔下鸡蛋,它没有碎,则这个鸡蛋你可以继续用
  • 如果这个鸡蛋摔碎了,则你可以用来测试的鸡蛋减少一个
  • 所有鸡蛋的质量相同(都会在同一楼层以上摔碎)
  • 对于一个鸡蛋,如果其在楼层i扔下的时候摔碎了,对于任何不小于i的楼层,这个鸡蛋都会被摔碎
  • 如果在楼层i扔下的时候没有摔碎,则对于任何不大于i的楼层,这颗鸡蛋也不会摔碎
  • 从第1层扔下,鸡蛋不一定完好,从第K层扔下,鸡蛋也不一定会摔碎。

三、解题思路:

  • 我们可以将这样的问题简记为W(n,k),其中n代表可用于测试的鸡蛋数,k代表被测试的楼层数。
  • 假设现在有2个鸡蛋,36层楼。
  • 对于问题W(2,36)我们可以如此考虑,将第1颗鸡蛋,在第i层扔下(i可以为1~k的任意值),
    • 如果碎了,则我们需要用第2颗鸡蛋,解决从第1层到第i-1层楼的子问题W(1,i-1),
    • 如果这颗鸡蛋没碎,则我们需要用这两颗鸡蛋,解决从i+1层到第36层的子问题W(2,36-i),
  • 解决这两个问题,可以分别得到一个尝试次数p,q,我们取这两个次数中的较大者(假设是p),与第1次在i层执行测试的这1次相加,则p+1就是第一次将鸡蛋仍在i层来解决W(2,36)所需的最少测试次数次数ti。
  • 对于36层楼的问题,第一次,我们可以把鸡蛋仍在36层中的任何一层,所以可以得到36中解决方案的测试次数T{t1,t2,t3,……,t36},在这些结果中,我们选取最小的ti,使得对于集合T中任意的值tj(1<=j<=36,j!=i),都有ti<=tj,则ti就是这个问题的答案。用公式来描述就是W(n, k) = 1 + min{max(W(n -1, x -1), W(n, k - x))}, x in {2, 3, ……,k},其中x是第一次的测试的楼层位置。
  • 其中W(1,k) = k(相当于1颗鸡蛋测试k层楼问题),W(0,k) = 0,W(n, 0) = 0
  • 所以在计算W(2,36)之前,我们需先计算出所有W(1,0),……,W(1,36),W(2,0),……,W(2,35)这些的值,可以用递推的方法实现
  • 时间复杂度:N*K*K
  • 空间复杂度:N*K

四、代码

#include <iostream>
#include <stack>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

int eggs, floors;
int result[1001][10001];

int DroppingEggsPuzzle()
{
    for(int i = 0; i < eggs + 1; i++)
    {
        result[i][0] = 0;
        result[i][1] = 1;

    }
    for(int j = 0; j < floors + 1; j++)
    {
        result[0][j] = 0;
        result[1][j] = j;

    }

    for(int i = 2; i <= eggs; i++)
    {
        for(int j = 2; j <= floors; j++)
        {
            result[i][j] = 0x7fffffff;
            for(int x = 1; x <= floors; x++)
            {
                int temp = max(result[i - 1][x - 1], result[i][j - x]) + 1;
                result[i][j] = min(result[i][j], temp);
            }
        }
    }
    return result[eggs][floors];
}

int main()
{
    while(scanf("%d %d", &eggs, &floors) != EOF)
    {
        cout << DroppingEggsPuzzle() << endl;
    }
    return 0;
}

五、空间复杂度优化

  • 从上述W(n, k) = 1 + min{max(W(n -1, x -1), W(n, k - x))}, x in {2, 3, ……,k}
  • 可知,W(n, k)只和W(n-1, k)有关,所以可以优化成用两个数组依次记录当前鸡蛋对应层数的值以及鸡蛋-1对应层数的值。
#include <iostream>
#include <stack>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

int eggs, floors;
int result[2][10001];//奇/偶数个鸡蛋对应第几层里面存放的值

int DroppingEggsPuzzle()
{

    for(int j = 0; j < floors + 1; j++)
    {
        result[0][j] = result[1][j] = j;
    }

    for(int i = 2; i <= eggs; i++)
    {
        for(int j = 2; j <= floors; j++)
        {
            int maxNum = 0x7fffffff, temp;
            for(int x = 1; x <= floors; x++)
            {
                temp = max(result[(i - 1) % 2][x - 1], result[i % 2][j - x]) + 1;
                maxNum = min(maxNum, temp);
            }
            result[i % 2][j] = maxNum;
        }
    }
    return result[eggs % 2][floors];
}

int main()
{
    while(scanf("%d %d", &eggs, &floors) != EOF)
    {
        cout << DroppingEggsPuzzle() << endl;
    }
    return 0;
}

六、时间复杂度优化

  • n个鸡蛋,测试x次(简记为D(n,x)),最大可以解决几层楼的问题
  • 给出楼层k求得D(n,x-1)
#include <iostream>
#include <stack>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

int eggs, floors;
int result[1001][32];

void DroppingEggsPuzzle()
{
    for(int x = 1; x < 32; x++)
        result[1][x] = x;

    for(int n = 2; n < 1001; n++)
    {
        for(int x = 2; x < 32; x++)
        {
            if(n >= x - 1) result[n][x - 1] = result[x - 1][x - 1];
            result[n][x] = result[n - 1][x - 1] + 1 + result[n][x - 1];
        }
    }
    //for(int n=1;n<=100;n++)
    //    for(int x = 1; x < 32; x++)
    //        printf("resutl[%d][%d]=%d\n",n,x,result[n][x]);

}

int findNum()
{
    for(int x = 1;; x++)
    {
        if(result[eggs][x] >= floors)
        {
            return x;
        }
    }
}

int main()
{
    DroppingEggsPuzzle();
    while(scanf("%d %d", &eggs, &floors) != EOF)
    {
        cout << findNum() << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值