2023-01-17 21:55阅读: 270评论: 0推荐: 0

【动态规划】高楼扔鸡蛋问题

高楼扔鸡蛋

这是一个比较经典的动态规划问题,最先来自谷歌的面试题。

题目

887. 鸡蛋掉落

方法一:动态规划

分析

我们假设 g(k,n) 表示当有 k 枚鸡蛋,楼层数为 n 时,找到临界楼层 F 所需要的最小操作次数。

边界条件

当楼层数为零时,查找次数为 0 ,当鸡蛋数量为 1 时,我们需要一层一层地查找,因此,最坏的情况下,查找次数等于楼层数,因此:

g(j,0)=0,0jkg(1,i)=i,1in

状态转移

我们考虑一般情况,对于第 i 层楼:

1, 2, , i1, i, i+1, , n1, n

当我们从第 i 层楼扔下一枚鸡蛋的时候,这枚鸡蛋有两种状态,鸡蛋碎了或者不碎,那么:

  • 若鸡蛋没有碎

    我们可以继续用 k 枚鸡蛋,在上方的楼层,即 i+1,,n,共 ni 层楼中,继续寻找 F,因此,所需要查找的次数为 g1=g(k,ni)

  • 若鸡蛋碎了

    这时,鸡蛋的数量少了一枚,因此,我们就需要用剩余的 k1 枚鸡蛋,在下方的楼层,即 1,,i1,共 i1 层楼中,继续寻找 F,因此所需要查找的次数为 g2=g(k1,i1)

这里,我们可以看到,通过状态转移,明显将问题的规模缩小了,由于题目是要求在最坏的情况下,扔鸡蛋的最少次数,所以,最坏的情况下,在第 i 层楼的操作次数,取决于 g1g2 的最大值。

因此,在最坏的情况下,最少的操作次数为:

g(k,n)=mini=1n(max(g1, g2))+1=mini=1n(max(g(k,ni), g(k1,i1)))+1

代码实现

  • 自顶向下的递归实现
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×n2)
  • 空间复杂度:O(k×n)

注:这种方法,直接枚举所有的状态,在力扣上提交的时候,会超时,因此,我们还需要对上述算法做进一步优化。

方法二:动态规划 + 二分搜索

分析

这里,我们定义一个函数 f(k,i),令

f(k,i)=maxi=1n(g1, g2)=maxi=1n(g(k,ni), g(k1,i1))

结合前面的分析,我们可以看出,当 k 不变时,即鸡蛋的个数不变时,函数 g1 和 函数 g2 只受楼层高度 i 影响,即:

  • g1=g(k, ni), 1[i,n] 是一个随 i 增加的单调递减函数;
  • g2=g(k1, i1), 1[i,n] 是一个随 i 增加的单调递增函数;

对于函数 g1 和 函数 g2 在区间 [i,n] 内,因为变量 i 只能取区间 [i,n]离散的整数,因此,在区间 [i,n] 内必然存在一个点 (k0,i0) 使得单调函数 g1 和单调函数 g2 的值相等,这里,我们假设函数f(k,i)的最小值是 f(k0,n0)

因此,我们可以将函数 f(k,i)表示为:

f(k,i)={g(k, ni),1ii0g(k1, i1),i0in

那么,状态转移方程,就可以写成:

g(k,n)=mini=1n(f(k,i))=f(k0,n0)

那么,我们只需要将优化的重点,放在求解 f(k0,n0) 上即可。对于单调函数,在区间 [i,n] 内我们可以通过二分法的方式缩小查找区间,求解 f(k0,n0)

注意:

  • 函数 g1 和 函数 g2 并非严格递增或者递减;
  • 我们可以通过数学方式证明,使得函数 g1 和 函数 g2 在区间 [i,n] 相等的点有两个,分别为 (k0,i0)(k1,i1),且 i0i1 相差 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×n×logn)
  • 空间复杂度:O(k×n)

本文作者:LARRY1024

本文链接:https://www.cnblogs.com/larry1024/p/17057913.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   LARRY1024  阅读(270)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.