Facebook Pixel

887. Super Egg Drop

Problem Description

You have k identical eggs and a building with n floors (numbered from 1 to n). There exists a critical floor f (where 0 ≤ f ≤ n) with the following properties:

  • Any egg dropped from a floor higher than f will break
  • Any egg dropped from floor f or below will not break

In each move, you can:

  1. Take an unbroken egg and drop it from any floor x (where 1 ≤ x ≤ n)
  2. If the egg breaks, you cannot use it again
  3. If the egg doesn't break, you can reuse it in future moves

Your goal is to find the minimum number of moves required to determine the exact value of f with certainty (in the worst case).

For example, if you have 2 eggs and 10 floors:

  • You need to find a strategy that guarantees finding the critical floor f
  • The strategy should minimize the maximum number of drops needed in the worst-case scenario
  • You must be able to determine f exactly, even if all but one egg breaks

The challenge is to develop an optimal strategy that balances:

  • Using eggs efficiently (since broken eggs cannot be reused)
  • Minimizing the total number of drops needed in the worst case
  • Ensuring you can always determine f regardless of where it actually is
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about this problem as a decision tree where each node represents dropping an egg from a certain floor. When we drop an egg from floor x:

  • If it breaks, we know f must be below x, and we lose one egg
  • If it doesn't break, we know f is at or above x, and we keep all eggs

The key insight is that this is a minimax problem. We want to minimize the maximum number of moves needed in the worst case. At each step, we need to consider: what's the worst that could happen after dropping from floor x?

Let's define dfs(i, j) as the minimum number of moves needed to determine f with certainty when we have i floors to check and j eggs available.

When we drop an egg from floor mid (where mid is between 1 and i):

  • If egg breaks: We have mid - 1 floors below to check with j - 1 eggs
  • If egg doesn't break: We have i - mid floors above to check with j eggs (same number of eggs)

Since we don't know which case will happen, we must prepare for the worst: max(dfs(mid - 1, j - 1), dfs(i - mid, j)) + 1

But which floor should we choose to drop from? We want to minimize this worst-case scenario. The brilliant observation is that:

  • dfs(mid - 1, j - 1) increases as mid increases (more floors to check below if egg breaks)
  • dfs(i - mid, j) decreases as mid increases (fewer floors to check above if egg doesn't break)

Since one function increases and the other decreases, their maximum is minimized when they're approximately equal! This is why we use binary search to find the optimal dropping floor - we're looking for the point where these two values are closest to each other.

The binary search finds the floor where dfs(mid - 1, j - 1) ≤ dfs(i - mid, j) transitions to dfs(mid - 1, j - 1) > dfs(i - mid, j), which gives us the optimal balance between the two worst-case scenarios.

Base cases are intuitive:

  • If i < 1: No floors to check, return 0
  • If j = 1: Only one egg, must check linearly from bottom, return i

Learn more about Math, Binary Search and Dynamic Programming patterns.

Solution Approach

The solution implements a dynamic programming approach with memoization using a recursive function dfs(i, j) that represents the minimum number of moves needed to determine the critical floor with i floors to check and j eggs available.

Implementation Details:

  1. Main Function Setup:

    • The function dfs(n, k) is called where n is the number of floors and k is the number of eggs
    • The @cache decorator is used for memoization to avoid recalculating the same subproblems
  2. Base Cases:

    if i < 1:
        return 0  # No floors to check
    if j == 1:
        return i  # Only one egg left, must check linearly
  3. Binary Search for Optimal Floor:

    l, r = 1, i
    while l < r:
        mid = (l + r + 1) >> 1  # Equivalent to (l + r + 1) // 2
        a = dfs(mid - 1, j - 1)  # Egg breaks case
        b = dfs(i - mid, j)      # Egg doesn't break case
        if a <= b:
            l = mid
        else:
            r = mid - 1

    The binary search finds the floor l where:

    • dfs(l - 1, j - 1) ≤ dfs(i - l, j) but
    • dfs(l, j - 1) > dfs(i - l - 1, j)

    This is the point where the two worst-case scenarios are most balanced.

  4. Computing the Result:

    return max(dfs(l - 1, j - 1), dfs(i - l, j)) + 1

    After finding the optimal floor l, we compute:

    • The worst case between egg breaking (l - 1 floors with j - 1 eggs) and not breaking (i - l floors with j eggs)
    • Add 1 for the current drop

Why Binary Search Works:

The key insight is that as we increase the dropping floor from 1 to i:

  • dfs(mid - 1, j - 1) monotonically increases (more floors to check if egg breaks)
  • dfs(i - mid, j) monotonically decreases (fewer floors to check if egg doesn't break)

The optimal floor is where these two values are closest, minimizing their maximum. The binary search efficiently finds this crossover point.

Time Complexity: O(k * n * log n) where we have at most O(k * n) unique states, and each state requires O(log n) time for binary search.

Space Complexity: O(k * n) for the memoization cache storing all possible states.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with 2 eggs and 6 floors to illustrate the solution approach.

Problem Setup: We need to find the minimum number of moves to determine the critical floor f in the worst case.

Step 1: Initial Call

  • Call dfs(6, 2) - we have 6 floors to check and 2 eggs available

Step 2: Binary Search for Optimal Floor

  • We need to find the best floor to drop from among floors 1-6
  • Initialize: l = 1, r = 6

First Binary Search Iteration:

  • mid = (1 + 6 + 1) / 2 = 4
  • Calculate:
    • a = dfs(3, 1) (if egg breaks at floor 4: check floors 1-3 with 1 egg)
    • b = dfs(2, 2) (if egg doesn't break: check floors 5-6 with 2 eggs)

Let's evaluate these recursive calls:

  • dfs(3, 1) = 3 (with 1 egg, must check linearly: worst case is 3 drops)
  • dfs(2, 2) needs further calculation...

Computing dfs(2, 2):

  • Binary search: l = 1, r = 2
  • mid = (1 + 2 + 1) / 2 = 2
  • a = dfs(1, 1) = 1 (1 floor with 1 egg)
  • b = dfs(0, 2) = 0 (0 floors to check)
  • Since a > b, set r = 1
  • Final: drop from floor 1
  • Result: max(dfs(0, 1), dfs(1, 2)) + 1 = max(0, 1) + 1 = 2

Back to Main Calculation:

  • Now we know: a = dfs(3, 1) = 3 and b = dfs(2, 2) = 2
  • Since a > b, set r = 3

Second Binary Search Iteration:

  • l = 1, r = 3
  • mid = (1 + 3 + 1) / 2 = 2
  • a = dfs(1, 1) = 1 (if egg breaks at floor 2)
  • b = dfs(4, 2) (if egg doesn't break: check floors 3-6)

Computing dfs(4, 2):

  • Through similar binary search process: optimal is floor 2
  • Result: max(dfs(1, 1), dfs(2, 2)) + 1 = max(1, 2) + 1 = 3

Back to Main:

  • a = 1, b = 3
  • Since a ≤ b, set l = 2

Third Binary Search Iteration:

  • l = 2, r = 3
  • mid = (2 + 3 + 1) / 2 = 3
  • a = dfs(2, 1) = 2
  • b = dfs(3, 2) = 2
  • Since a ≤ b, set l = 3
  • Now l = r = 3, binary search ends

Step 3: Final Result

  • Optimal floor to drop from is 3
  • Result: max(dfs(2, 1), dfs(3, 2)) + 1 = max(2, 2) + 1 = 3

Answer: 3 moves minimum in the worst case

Verification of Strategy: The optimal strategy is:

  1. First drop from floor 3
    • If breaks: check floors 1-2 with 1 egg (max 2 more drops)
    • If doesn't break: check floors 4-6 with 2 eggs (max 2 more drops)
  2. Total worst case: 1 + 2 = 3 drops

This demonstrates how the binary search finds the floor (3) where the two worst-case scenarios are balanced (both requiring 2 additional drops), minimizing the overall worst case.

Solution Implementation

1class Solution:
2    def superEggDrop(self, k: int, n: int) -> int:
3        from functools import cache
4      
5        @cache
6        def dp(floors: int, eggs: int) -> int:
7            """
8            Calculate minimum number of moves needed in worst case
9            to find critical floor with given number of floors and eggs.
10          
11            Args:
12                floors: Number of floors to check
13                eggs: Number of eggs available
14          
15            Returns:
16                Minimum number of moves needed in worst case
17            """
18            # Base case: No floors to check
19            if floors < 1:
20                return 0
21          
22            # Base case: Only 1 egg left, must check linearly from bottom
23            if eggs == 1:
24                return floors
25          
26            # Binary search to find optimal floor to drop egg from
27            # We're looking for the floor that minimizes the worst case
28            left, right = 1, floors
29          
30            while left < right:
31                # Calculate middle point (ceiling division)
32                mid = (left + right + 1) >> 1
33              
34                # Case 1: Egg breaks at floor mid
35                # Check floors below with one less egg
36                egg_breaks = dp(mid - 1, eggs - 1)
37              
38                # Case 2: Egg doesn't break at floor mid  
39                # Check floors above with same number of eggs
40                egg_survives = dp(floors - mid, eggs)
41              
42                # Binary search to find the floor where the two cases balance
43                # This minimizes the maximum of the two cases
44                if egg_breaks <= egg_survives:
45                    left = mid
46                else:
47                    right = mid - 1
48          
49            # Return worst case scenario (max of two cases) plus current move
50            return max(dp(left - 1, eggs - 1), dp(floors - left, eggs)) + 1
51      
52        # Start with n floors and k eggs
53        return dp(n, k)
54
1class Solution {
2    // Memoization table: memo[floors][eggs] stores minimum moves needed
3    private int[][] memo;
4
5    /**
6     * Finds the minimum number of moves required in worst case to find critical floor
7     * @param k - number of eggs available
8     * @param n - number of floors to test
9     * @return minimum number of moves in worst case
10     */
11    public int superEggDrop(int k, int n) {
12        memo = new int[n + 1][k + 1];
13        return findMinMoves(n, k);
14    }
15
16    /**
17     * Recursive helper function with memoization to find minimum moves
18     * @param floors - number of floors remaining to test
19     * @param eggs - number of eggs remaining
20     * @return minimum number of moves needed in worst case
21     */
22    private int findMinMoves(int floors, int eggs) {
23        // Base case: no floors to test
24        if (floors < 1) {
25            return 0;
26        }
27      
28        // Base case: only one egg left, must test linearly from bottom
29        if (eggs == 1) {
30            return floors;
31        }
32      
33        // Return memoized result if already computed
34        if (memo[floors][eggs] != 0) {
35            return memo[floors][eggs];
36        }
37      
38        // Binary search to find optimal floor to drop egg from
39        int left = 1;
40        int right = floors;
41      
42        while (left < right) {
43            int mid = (left + right + 1) >> 1;  // Equivalent to (left + right + 1) / 2
44          
45            // Calculate worst case moves for both scenarios
46            int eggBreaks = findMinMoves(mid - 1, eggs - 1);     // Egg breaks: check floors below
47            int eggSurvives = findMinMoves(floors - mid, eggs);  // Egg survives: check floors above
48          
49            // Binary search optimization: find floor where both cases are balanced
50            if (eggBreaks <= eggSurvives) {
51                left = mid;  // Move search to higher floors
52            } else {
53                right = mid - 1;  // Move search to lower floors
54            }
55        }
56      
57        // Calculate minimum moves for optimal floor (left)
58        // Take maximum of two cases (worst case) and add 1 for current move
59        memo[floors][eggs] = Math.max(
60            findMinMoves(left - 1, eggs - 1),      // Egg breaks case
61            findMinMoves(floors - left, eggs)      // Egg survives case
62        ) + 1;
63      
64        return memo[floors][eggs];
65    }
66}
67
1class Solution {
2public:
3    int superEggDrop(int k, int n) {
4        // dp[floors][eggs] = minimum number of moves needed in worst case
5        // to determine critical floor with 'eggs' eggs and 'floors' floors
6        int dp[n + 1][k + 1];
7        memset(dp, 0, sizeof(dp));
8      
9        // Recursive function with memoization to find minimum moves
10        // Parameters: numFloors - number of floors to check
11        //            numEggs - number of eggs available
12        // Returns: minimum number of moves needed in worst case
13        auto findMinMoves = [&](this auto&& findMinMoves, int numFloors, int numEggs) -> int {
14            // Base case: no floors to check
15            if (numFloors < 1) {
16                return 0;
17            }
18          
19            // Base case: only one egg left, must check linearly from bottom
20            if (numEggs == 1) {
21                return numFloors;
22            }
23          
24            // Return memoized result if already computed
25            if (dp[numFloors][numEggs]) {
26                return dp[numFloors][numEggs];
27            }
28          
29            // Binary search to find optimal floor to drop egg from
30            // We want to minimize the maximum of two scenarios
31            int left = 1, right = numFloors;
32            while (left < right) {
33                int mid = (left + right + 1) >> 1;
34              
35                // Scenario 1: egg breaks at floor 'mid'
36                // Check floors below with one less egg
37                int eggBreaks = findMinMoves(mid - 1, numEggs - 1);
38              
39                // Scenario 2: egg doesn't break at floor 'mid'
40                // Check floors above with same number of eggs
41                int eggSurvives = findMinMoves(numFloors - mid, numEggs);
42              
43                // Binary search to find the floor where the two scenarios
44                // are most balanced (minimizing the maximum)
45                if (eggBreaks <= eggSurvives) {
46                    left = mid;
47                } else {
48                    right = mid - 1;
49                }
50            }
51          
52            // Store and return the result: maximum of two scenarios + 1 (current move)
53            // We take the maximum because we need to prepare for worst case
54            return dp[numFloors][numEggs] = max(findMinMoves(left - 1, numEggs - 1), 
55                                                findMinMoves(numFloors - left, numEggs)) + 1;
56        };
57      
58        return findMinMoves(n, k);
59    }
60};
61
1/**
2 * Determines the minimum number of moves to find the critical floor
3 * with k eggs and n floors using dynamic programming with binary search optimization
4 * @param k - number of eggs available
5 * @param n - number of floors to test
6 * @returns minimum number of moves in worst case
7 */
8function superEggDrop(k: number, n: number): number {
9    // Memoization table: memo[floors][eggs] stores minimum moves needed
10    const memo: number[][] = new Array(n + 1)
11        .fill(0)
12        .map(() => new Array(k + 1).fill(0));
13  
14    /**
15     * Recursive function to find minimum moves for given floors and eggs
16     * @param floors - number of floors to check
17     * @param eggs - number of eggs available
18     * @returns minimum number of moves needed in worst case
19     */
20    const findMinMoves = (floors: number, eggs: number): number => {
21        // Base case: no floors to check
22        if (floors < 1) {
23            return 0;
24        }
25      
26        // Base case: only one egg, must check linearly from bottom
27        if (eggs === 1) {
28            return floors;
29        }
30      
31        // Return memoized result if already computed
32        if (memo[floors][eggs] !== 0) {
33            return memo[floors][eggs];
34        }
35      
36        // Binary search to find optimal floor to drop egg from
37        let left = 1;
38        let right = floors;
39      
40        while (left < right) {
41            const mid = Math.floor((left + right + 1) / 2);
42          
43            // Case 1: egg breaks, check floors below with one less egg
44            const eggBreaks = findMinMoves(mid - 1, eggs - 1);
45          
46            // Case 2: egg doesn't break, check floors above with same eggs
47            const eggSurvives = findMinMoves(floors - mid, eggs);
48          
49            // Binary search: find the floor where worst cases are balanced
50            if (eggBreaks <= eggSurvives) {
51                left = mid;
52            } else {
53                right = mid - 1;
54            }
55        }
56      
57        // Calculate minimum moves: max of two cases (worst case) + 1 for current move
58        const result = Math.max(
59            findMinMoves(left - 1, eggs - 1),    // egg breaks at optimal floor
60            findMinMoves(floors - left, eggs)     // egg survives at optimal floor
61        ) + 1;
62      
63        // Store result in memoization table and return
64        memo[floors][eggs] = result;
65        return result;
66    };
67  
68    // Start the recursive search with n floors and k eggs
69    return findMinMoves(n, k);
70}
71

Time and Space Complexity

Time Complexity: O(k * n * log n)

The analysis breaks down as follows:

  • The recursive function dfs(i, j) has at most n * k unique states since i ranges from 0 to n and j ranges from 0 to k
  • Due to memoization with @cache, each state is computed only once
  • For each state (i, j), the function performs a binary search from 1 to i, which takes O(log i) iterations, where i ≤ n
  • In each iteration of the binary search, we make recursive calls to dfs(mid - 1, j - 1) and dfs(i - mid, j), but these are memoized lookups with O(1) access time after initial computation
  • Therefore, the total time complexity is O(k * n * log n)

Space Complexity: O(k * n)

The space complexity consists of:

  • The memoization cache stores at most O(k * n) entries (one for each unique state)
  • The recursion stack depth is at most O(k) in the worst case, as we decrease j by 1 in each recursive level until reaching the base case where j = 1
  • Since O(k * n) > O(k) for all valid inputs, the overall space complexity is O(k * n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Binary Search Boundary Logic

One of the most common mistakes is implementing the binary search incorrectly when finding the optimal floor to drop from. The binary search is looking for a specific crossover point where the two scenarios (egg breaks vs. egg doesn't break) are most balanced.

Pitfall Example:

# WRONG: Using standard binary search template without considering the specific condition
while left <= right:  # Wrong condition
    mid = (left + right) // 2  # May miss the optimal point
    egg_breaks = dp(mid - 1, eggs - 1)
    egg_survives = dp(floors - mid, eggs)
  
    if egg_breaks < egg_survives:  # Wrong: using < instead of <=
        left = mid + 1
    else:
        right = mid - 1

Why it's wrong: The standard binary search template doesn't work here because we're not looking for an exact value match. We're searching for the floor where dp(floor - 1, eggs - 1) <= dp(floors - floor, eggs) transitions to dp(floor, eggs - 1) > dp(floors - floor - 1, eggs).

Solution:

# CORRECT: Use ceiling division and proper boundary conditions
while left < right:  # Stop when left == right
    mid = (left + right + 1) >> 1  # Ceiling division to avoid infinite loop
    egg_breaks = dp(mid - 1, eggs - 1)
    egg_survives = dp(floors - mid, eggs)
  
    if egg_breaks <= egg_survives:  # Use <= to find the rightmost valid position
        left = mid  # Don't skip mid
    else:
        right = mid - 1

2. Off-by-One Errors in Recursive Calls

Another frequent mistake involves incorrect floor calculations when making recursive calls, especially after an egg breaks or doesn't break.

Pitfall Example:

# WRONG: Incorrect floor count calculations
egg_breaks = dp(mid, eggs - 1)      # Should be mid - 1
egg_survives = dp(floors - mid + 1, eggs)  # Should be floors - mid

Why it's wrong:

  • If we drop from floor mid and the egg breaks, we know the critical floor is in [1, mid-1], not [1, mid]
  • If the egg doesn't break at floor mid, we need to check floors [mid+1, floors], which is floors - mid floors, not floors - mid + 1

Solution:

# CORRECT: Accurate floor calculations
egg_breaks = dp(mid - 1, eggs - 1)    # Check floors 1 to mid-1
egg_survives = dp(floors - mid, eggs)  # Check floors mid+1 to floors

3. Forgetting to Add 1 for the Current Move

A subtle but critical error is forgetting to account for the current drop when calculating the total number of moves.

Pitfall Example:

# WRONG: Forgetting to add the current move
return max(dp(left - 1, eggs - 1), dp(floors - left, eggs))

Solution:

# CORRECT: Add 1 for the current drop
return max(dp(left - 1, eggs - 1), dp(floors - left, eggs)) + 1

4. Inefficient Linear Search Instead of Binary Search

Some implementations might use a linear search to find the optimal floor, which significantly increases time complexity.

Pitfall Example:

# WRONG: Linear search through all possible floors
min_moves = float('inf')
for floor in range(1, floors + 1):
    egg_breaks = dp(floor - 1, eggs - 1)
    egg_survives = dp(floors - floor, eggs)
    worst_case = max(egg_breaks, egg_survives) + 1
    min_moves = min(min_moves, worst_case)
return min_moves

Why it's wrong: This approach has O(n) complexity per state instead of O(log n), making the overall complexity O(k * n²) instead of O(k * n * log n).

Solution: Use the binary search approach as shown in the correct implementation to efficiently find the optimal floor in O(log n) time.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which of these properties could exist for a graph but not a tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More