鸡蛋掉落问题(K个鸡蛋,N层楼版本)
问题描述
你面前有一栋从1到N共N层的建筑,每个楼层都一样高。现在给你K个完全相同的鸡蛋,你可以进行以下操作:
- 选择一个楼层x(1 ≤ x ≤ N),将一个鸡蛋从x层扔下
- 如果鸡蛋在x层摔碎(f ≤ x),那么你就失去了这个鸡蛋
- 如果鸡蛋在x层没有摔碎(f > x),你可以回收这个鸡蛋继续使用
你需要确定建筑物中最高的不会使鸡蛋摔碎的楼层f(即鸡蛋从f层及以下摔下不会碎,从f+1层及以上摔下会碎)。
请你计算在最坏情况下,你最少需要扔多少次鸡蛋才能确定f的值?
解题思路分析
这是一个经典的区间动态规划问题,我们需要考虑鸡蛋数量K和楼层数N两个维度。最坏情况下的最少尝试次数意味着我们需要考虑所有可能情况中的最坏情况,并选择最优策略。
动态规划解法
1. 状态定义
定义dp[k][n]表示有k个鸡蛋,面对n层楼时,在最坏情况下确定f值所需的最少尝试次数。
2. 基础情况
- 当k = 1时(只有1个鸡蛋):只能从低到高逐层尝试,最坏情况需要n次尝试
- 当n = 0时:没有楼层,不需要尝试
- 当n = 1时:只有1层楼,只需要1次尝试
3. 状态转移方程
对于状态dp[k][n],我们考虑第一次尝试从第i层(1 ≤ i ≤ n)扔鸡蛋:
情况1:鸡蛋碎了
- 说明f < i,我们需要在下面的i-1层中继续寻找
- 剩余鸡蛋:k-1个
- 剩余楼层:i-1层
- 所需次数:1 + dp[k-1][i-1]
情况2:鸡蛋没碎
- 说明f ≥ i,我们需要在上面的n-i层中继续寻找
- 剩余鸡蛋:k个(鸡蛋回收)
- 剩余楼层:n-i层
- 所需次数:1 + dp[k][n-i]
由于要考虑最坏情况,我们取两种情况的最大值:
max(dp[k-1][i-1], dp[k][n-i]) + 1
然后我们遍历所有可能的i(1 ≤ i ≤ n),选择其中的最小值:
dp[k][n] = min{max(dp[k-1][i-1], dp[k][n-i]) + 1} for i = 1 to n
4. 算法实现
def superEggDrop(K, N):
# 初始化DP表
dp = [[0] * (N + 1) for _ in range(K + 1)]
# 基础情况
for i in range(1, K + 1):
dp[i][1] = 1 # 只有1层楼
for j in range(1, N + 1):
dp[1][j] = j # 只有1个鸡蛋
# 填充DP表
for k in range(2, K + 1): # 鸡蛋数量从2到K
for n in range(2, N + 1): # 楼层数量从2到N
# 二分查找优化
left, right = 1, n
while left < right:
mid = (left + right + 1) // 2
broken = dp[k-1][mid-1] # 鸡蛋碎了
not_broken = dp[k][n-mid] # 鸡蛋没碎
if broken > not_broken:
right = mid - 1
else:
left = mid
dp[k][n] = max(dp[k-1][left-1], dp[k][n-left]) + 1
return dp[K][N]
5. 复杂度优化
上面的解法时间复杂度为O(KN²),当N很大时会超时。我们可以使用二分查找优化:
观察状态转移方程,dp[k-1][i-1]随着i单调递增,dp[k][n-i]随着i单调递减。我们可以在i的取值范围内使用二分查找找到这两个函数的交点,从而快速找到最优的i。
6. 示例说明
以K=2, N=6为例:
- dp[1][1-6] = [1,2,3,4,5,6](只有1个鸡蛋)
- 计算dp[2][6]:
- i=3: max(dp[1][2], dp[2][3])+1 = max(2,2)+1=3
- i=4: max(dp[1][3], dp[2][2])+1 = max(3,2)+1=4
- 最小值在i=3时取得,结果为3
所以2个鸡蛋6层楼最少需要3次尝试。
总结
这个问题通过动态规划将复杂的最优化问题分解为子问题,通过状态转移方程系统地求解。二分查找的优化技巧大大提高了算法效率,是区间动态规划中一个经典的优化技巧。