You are given k
identical eggs and you have access to a building with n
floors labeled from 1
to n
.
You know that there exists a floor f
where 0 <= f <= n
such that any egg dropped at a floor higher than f
will break, and any egg dropped at or below floor f
will not break.
Each move, you may take an unbroken egg and drop it from any floor x
(where 1 <= x <= n
). If the egg breaks, you can no longer use it. However, if the egg does not break, you may reuse it in future moves.
Return the minimum number of moves that you need to determine with certainty what the value of f
is.
Example 1:
Input: k = 1, n = 2
Output: 2
Explanation:
Drop the egg from floor 1. If it breaks, we know that f = 0.
Otherwise, drop the egg from floor 2. If it breaks, we know that f = 1.
If it does not break, then we know f = 2.
Hence, we need at minimum 2 moves to determine with certainty what the value of f is.
Example 2:
Input: k = 2, n = 6
Output: 3
Example 3:
Input: k = 3, n = 14
Output: 4
Constraints:
1 <= k <= 100
1 <= n <= 10^4
The egg dropping problem is a classic dynamic programming problem. The goal is to find the minimum number of moves needed to determine the critical floor f
with k
eggs and n
floors.
A simple approach is to try every possible floor to drop the egg. For each floor x
, there are two possibilities:
k-1
eggs and x-1
floors to check.k
eggs and n-x
floors to check.We want to minimize the worst-case scenario, so we take the maximum of these two possibilities and add 1 (for the current drop). We recursively apply this logic.
def egg_drop_recursive(k, n):
if n == 0 or n == 1:
return n
if k == 1:
return n
min_attempts = float('inf')
for x in range(1, n + 1):
attempts = 1 + max(egg_drop_recursive(k - 1, x - 1), # Egg breaks
egg_drop_recursive(k, n - x)) # Egg doesn't break
min_attempts = min(min_attempts, attempts)
return min_attempts
This naive approach leads to exponential time complexity because the same subproblems are solved repeatedly.
To optimize, we use dynamic programming to store the results of subproblems and avoid recomputation. We create a 2D table dp[k][n]
where dp[i][j]
represents the minimum number of moves needed to find the critical floor with i
eggs and j
floors.
def egg_drop_dp(k, n):
dp = [[0] * (n + 1) for _ in range(k + 1)]
# If we have only one floor, one attempt is needed, if no floor - no attempts
for i in range(1, k + 1):
dp[i][1] = 1
dp[i][0] = 0
# If we have only one egg, we have to check every floor linearly
for j in range(1, n + 1):
dp[1][j] = j
for i in range(2, k + 1):
for j in range(2, n + 1):
dp[i][j] = float('inf')
for x in range(1, j + 1):
attempts = 1 + max(dp[i - 1][x - 1], # Egg breaks
dp[i][j - x]) # Egg doesn't break
dp[i][j] = min(dp[i][j], attempts)
return dp[k][n]
dp
table of size k * n
and for each cell, we iterate up to n
times.dp
table.The time complexity can be further optimized by using binary search instead of linear search to find the optimal floor x
. The observation is that dp[i - 1][x - 1]
is an increasing function with respect to x
and dp[i][j - x]
is a decreasing function with respect to x
. Therefore, their maximum can be found using binary search.
def egg_drop_dp_binary_search(k, n):
dp = [[0] * (n + 1) for _ in range(k + 1)]
for i in range(1, k + 1):
dp[i][1] = 1
dp[i][0] = 0
for j in range(1, n + 1):
dp[1][j] = j
for i in range(2, k + 1):
for j in range(2, n + 1):
low = 1
high = j
dp[i][j] = float('inf')
while low <= high:
mid = (low + high) // 2
break_case = dp[i - 1][mid - 1]
not_break_case = dp[i][j - mid]
attempts = 1 + max(break_case, not_break_case)
dp[i][j] = min(dp[i][j], attempts)
if break_case < not_break_case:
low = mid + 1
else:
high = mid - 1
return dp[k][n]
dp
table.dp
table.The space complexity can be further reduced to O(n) by using only two rows of the DP table at a time, since the current row only depends on the previous row.
n
is 0 or 1, the answer is n
.k
is 1, the answer is n
(we have to try each floor linearly).