【动态规划】高楼扔鸡蛋问题
高楼扔鸡蛋
这是一个比较经典的动态规划问题,最先来自谷歌的面试题。
题目
方法一:动态规划
分析
我们假设 \(g(k, n)\) 表示当有 \(k\) 枚鸡蛋,楼层数为 \(n\) 时,找到临界楼层 \(F\) 所需要的最小操作次数。
边界条件
当楼层数为零时,查找次数为 \(0\) ,当鸡蛋数量为 \(1\) 时,我们需要一层一层地查找,因此,最坏的情况下,查找次数等于楼层数,因此:
状态转移
我们考虑一般情况,对于第 \(i\) 层楼:
当我们从第 \(i\) 层楼扔下一枚鸡蛋的时候,这枚鸡蛋有两种状态,鸡蛋碎了或者不碎,那么:
-
若鸡蛋没有碎
我们可以继续用 \(k\) 枚鸡蛋,在上方的楼层,即 \(i + 1, \cdots, n\),共 \(n - i\) 层楼中,继续寻找 \(F\),因此,所需要查找的次数为 \(g_1=g(k, n - i)\) ;
-
若鸡蛋碎了
这时,鸡蛋的数量少了一枚,因此,我们就需要用剩余的 \(k - 1\) 枚鸡蛋,在下方的楼层,即 \(1, \cdots, i - 1\),共 \(i - 1\) 层楼中,继续寻找 \(F\),因此所需要查找的次数为 \(g_2=g(k - 1, i - 1)\) 。
这里,我们可以看到,通过状态转移,明显将问题的规模缩小了,由于题目是要求在最坏的情况下,扔鸡蛋的最少次数,所以,最坏的情况下,在第 \(i\) 层楼的操作次数,取决于 \(g_1\) 和 \(g_2\) 的最大值。
因此,在最坏的情况下,最少的操作次数为:
代码实现
- 自顶向下的递归实现
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
memory = dict()
return self.drop_egg(k, n, memory)
def drop_egg(self, k: int, n: int, memory) -> int:
if n == 0:
return 0
if k == 1:
return n
if (k, n) in memory:
return memory.get((k, n))
result = float("INF")
for i in range(1, n + 1):
g1 = self.drop_egg(k, n - i, memory)
g2 = self.drop_egg(k - 1, i - 1, memory)
result = min(result, max(g1, g2) + 1)
memory[(k, n)] = result
return result
- 自底向上的迭代实现
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
return self.drop_egg(k, n)
def drop_egg(self, eggs: int, floor: int) -> int:
dp = [[float("INF") for _ in range(floor + 1)] for _ in range(eggs + 1)]
# 鸡蛋数为1时
for n in range(1, floor + 1):
dp[1][n] = n
# 楼层数为0
for i in range(0, eggs + 1):
dp[i][0] = 0
# 楼层数为1
dp[0][1] = 0
for i in range(1, eggs + 1):
dp[i][1] = 1
for k in range(2, eggs + 1):
for n in range(2, floor + 1):
for i in range(1, n + 1):
g1 = dp[k][n - i]
g2 = dp[k - 1][i - 1]
dp[k][n] = min(dp[k][n], max(g1, g2) + 1)
return int(dp[eggs][floor])
复杂度
复杂度分析:
- 时间复杂度:\(O(k \times n^2)\);
- 空间复杂度:\(O(k \times n)\)。
注:这种方法,直接枚举所有的状态,在力扣上提交的时候,会超时,因此,我们还需要对上述算法做进一步优化。
方法二:动态规划 + 二分搜索
分析
这里,我们定义一个函数 \(f(k, i)\),令
结合前面的分析,我们可以看出,当 \(k\) 不变时,即鸡蛋的个数不变时,函数 \(g_1\) 和 函数 \(g_2\) 只受楼层高度 \(i\) 影响,即:
- \(g_1=g(k, \ n - i), \ 1 \in [i, n]\) 是一个随 \(i\) 增加的单调递减函数;
- \(g_2=g(k - 1, \ i - 1),\ 1 \in [i, n]\) 是一个随 \(i\) 增加的单调递增函数;
对于函数 \(g_1\) 和 函数 \(g_2\) 在区间 \([i, n]\) 内,因为变量 \(i\) 只能取区间 \([i, n]\) 内离散的整数,因此,在区间 \([i, n]\) 内必然存在一个点 \((k_0, i_0)\) 使得单调函数 \(g_1\) 和单调函数 \(g_2\) 的值相等,这里,我们假设函数\(f(k, i)\)的最小值是 \(f(k_0,n_0)\)。
因此,我们可以将函数 \(f(k, i)\)表示为:
那么,状态转移方程,就可以写成:
那么,我们只需要将优化的重点,放在求解 \(f(k_0,n_0)\) 上即可。对于单调函数,在区间 \([i, n]\) 内我们可以通过二分法的方式缩小查找区间,求解 \(f(k_0,n_0)\)。
注意:
- 函数 \(g_1\) 和 函数 \(g_2\) 并非严格递增或者递减;
- 我们可以通过数学方式证明,使得函数 \(g_1\) 和 函数 \(g_2\) 在区间 \([i, n]\) 相等的点有两个,分别为 \((k_0, i_0)\) 和 \((k_1, i_1)\),且 \(i_0\) 和 \(i_1\) 相差 \(1\),我们并不需要关心具体值是多少,我们只需要记录它们所对应的函数值即可。
代码实现
- 自顶向下的递归实现
from typing import Dict
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
memory = dict()
return self.drop_egg(k, n, memory)
def drop_egg(self, k: int, n: int, memory: Dict) -> int:
if n == 0:
return 0
if k == 1:
return n
if (k, n) in memory:
return memory.get((k, n))
result = float("INF")
left, right = 1, n
while left <= right:
mid = left + (right - left) // 2
g1 = self.drop_egg(k, n - mid, memory)
g2 = self.drop_egg(k - 1, mid - 1, memory)
result = min(result, max(g1, g2) + 1)
if g1 < g2:
right = mid - 1
elif g1 > g2:
left = mid + 1
else:
break
memory[(k, n)] = result
return result
- 自底向上的迭代实现
class Solution:
def superEggDrop(self, k: int, n: int) -> int:
return self.drop_egg(k, n)
def drop_egg(self, eggs: int, floor: int) -> int:
dp = [[float("INF") for _ in range(floor + 1)] for _ in range(eggs + 1)]
# 鸡蛋数为1时
for n in range(1, floor + 1):
dp[1][n] = n
# 楼层数为0
for i in range(0, eggs + 1):
dp[i][0] = 0
# 楼层数为1
dp[0][1] = 0
for i in range(1, eggs + 1):
dp[i][1] = 1
for k in range(2, eggs + 1):
for n in range(2, floor + 1):
# 使用二分搜索代替迭代搜索
result = float("INF")
left, right = 1, n
while left <= right:
mid = left + (right - left) // 2
g1 = dp[k][n - mid]
g2 = dp[k - 1][mid - 1]
result = min(result, max(g1, g2) + 1)
if g1 < g2:
right = mid - 1
elif g1 > g2:
left = mid + 1
else:
break
dp[k][n] = result
return int(dp[eggs][floor])
复杂度
复杂度分析:
- 时间复杂度:\(O(k \times n \times logn)\);
- 空间复杂度:\(O(k \times n)\)。