返回

用鸡蛋的正确姿势——Google 经典算法面试题解答

前端

有人也许会想,先从一层楼摔,如果鸡蛋碎了,说明安全楼层就是一楼;如果没碎,继续往上,从二层摔,直到鸡蛋碎掉或安全到达100层。这种方法的最坏情况是需要100次尝试,平均情况是50次尝试。

但我们能做得更好吗?

从中间开始

我们可以从50层楼开始,如果鸡蛋碎了,说明安全楼层在149层;如果没碎,说明安全楼层在51100层。这样,我们就把100层楼的范围缩小到50层楼。

我们继续从25层楼开始,以此类推,直到找到安全楼层。最坏情况是需要14次尝试,平均情况是7次尝试。

递归和动态规划

上面的方法可以用递归来实现。我们可以定义一个函数 egg_drop(n, k),其中 n 是建筑物的层数,k 是鸡蛋的数量。该函数返回找到安全楼层所需的最小尝试次数。

def egg_drop(n, k):
    # 如果只有一个鸡蛋,那么最坏情况需要尝试 n 次
    if k == 1:
        return n

    # 如果没有鸡蛋,那么无论尝试多少次都找不到安全楼层
    if n == 0:
        return 0

    # 尝试从第 i 层楼摔鸡蛋
    for i in range(1, n):
        # 如果鸡蛋碎了,那么安全楼层就在第 i 层或以下
        if egg_drop(i - 1, k - 1) == 0:
            return i

        # 如果鸡蛋没碎,那么安全楼层就在第 i 层或以上
        if egg_drop(n - i, k) == 0:
            return i

    # 如果尝试了所有楼层,都没找到安全楼层,那么安全楼层就是第 n 层
    return n

该函数的时间复杂度是 O(nk^2)。

我们还可以用动态规划来实现该问题。我们可以定义一个二维数组 dp[n][k],其中 dp[n][k] 表示找到安全楼层所需的最小尝试次数。

def egg_drop_dp(n, k):
    # 初始化 dp 数组
    dp = [[0 for _ in range(k + 1)] for _ in range(n + 1)]

    # 如果只有一个鸡蛋,那么最坏情况需要尝试 n 次
    for i in range(1, n + 1):
        dp[i][1] = i

    # 如果没有鸡蛋,那么无论尝试多少次都找不到安全楼层
    for j in range(1, k + 1):
        dp[0][j] = 0

    # 尝试从第 i 层楼摔鸡蛋
    for i in range(1, n + 1):
        for j in range(2, k + 1):
            # 如果鸡蛋碎了,那么安全楼层就在第 i 层或以下
            for l in range(1, i):
                dp[i][j] = min(dp[i][j], 1 + max(dp[l - 1][j - 1], dp[i - l][j]))

            # 如果鸡蛋没碎,那么安全楼层就在第 i 层或以上
            dp[i][j] = min(dp[i][j], 1 + dp[i - 1][j])

    # 返回找到安全楼层所需的最小尝试次数
    return dp[n][k]

该函数的时间复杂度是 O(nk^2)。

总结

我们讨论了三种解决 Google 经典算法面试题的方法:暴力法、递归和动态规划。递归和动态规划的方法都可以将时间复杂度降低到 O(nk^2)。