Facebook Pixel

375. Guess Number Higher or Lower II

Problem Description

This is a guessing game where you need to find a secret number between 1 and n. The rules are:

  1. A secret number is picked between 1 and n (inclusive)
  2. You make guesses to find the number
  3. After each wrong guess, you're told if the secret number is higher or lower than your guess
  4. Each wrong guess costs you money equal to the number you guessed (e.g., guessing 5 costs $5)
  5. You win when you guess correctly, but you lose if you run out of money

The key challenge is that you don't know what the secret number is beforehand. You need to determine the minimum amount of money required to guarantee you can always win, regardless of what the secret number turns out to be.

This means finding the optimal guessing strategy that minimizes the worst-case cost. You need enough money to handle the most expensive possible scenario while using the best strategy.

For example, if n = 10 and you guess 7:

  • If the actual number is higher, you pay $7 and continue guessing in range [8, 10]
  • If the actual number is lower, you pay $7 and continue guessing in range [1, 6]
  • If you guessed correctly, you pay nothing

The problem asks for the minimum amount of money needed to guarantee finding any possible secret number between 1 and n using the optimal strategy.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to prepare for the worst-case scenario while playing optimally. When we make a guess, we don't know if the actual number is higher or lower, so we must have enough money to handle whichever path costs more.

Think about it this way: when we guess a number k in range [i, j], three things can happen:

  1. We guess correctly - cost is 0
  2. The number is lower - we pay k and continue in range [i, k-1]
  3. The number is higher - we pay k and continue in range [k+1, j]

Since we want to guarantee a win, we must prepare for the more expensive path between options 2 and 3. This is why we take the max of the two subproblems.

But we also have control over which number k to guess first. We want to choose the guess that minimizes our worst-case cost. This is why we take the min over all possible guesses k.

This naturally leads to a dynamic programming approach where f[i][j] represents the minimum money needed to guarantee finding any number in range [i, j]. For each range, we:

  1. Try every possible guess k in that range
  2. For each guess, calculate the worst-case cost: k + max(f[i][k-1], f[k+1][j])
  3. Choose the guess that gives us the minimum worst-case cost

We build up from smaller ranges to larger ones because to solve f[i][j], we need the solutions for all smaller subranges. Starting from single numbers (cost 0) and expanding outward ensures we always have the required subproblems solved.

The final answer is f[1][n] - the minimum money needed to guarantee finding any number from 1 to n.

Solution Approach

We use dynamic programming with a 2D table f[i][j] where each entry represents the minimum cost needed to guarantee finding any number in the range [i, j].

Initialization:

  • Create a 2D array f of size (n+1) × (n+1) initialized with zeros
  • Base case: f[i][i] = 0 for all i (no cost to guess when there's only one number)
  • When i > j, f[i][j] = 0 (invalid range)

Filling the DP Table: We process ranges in increasing order of size, iterating from bottom-right to top-left:

  • Outer loop: i from n-1 down to 1
  • Inner loop: j from i+1 to n

This order ensures that when computing f[i][j], all required subproblems f[i][k-1] and f[k+1][j] are already solved.

State Transition: For each range [i, j], we need to find the optimal guess:

  1. Initialize with the worst strategy: f[i][j] = j + f[i][j-1] (always guess the largest number)

  2. Try every possible guess k in range [i, j):

    • Cost of guessing k: k dollars
    • If the number is lower: need f[i][k-1] more dollars
    • If the number is higher: need f[k+1][j] more dollars
    • Worst case for this guess: k + max(f[i][k-1], f[k+1][j])
  3. Update with the minimum worst-case cost:

    f[i][j] = min(f[i][j], max(f[i][k-1], f[k+1][j]) + k)

Time Complexity: O(n³) - three nested loops over the range Space Complexity: O(n²) - for the DP table

The final answer is f[1][n], representing the minimum money needed to guarantee finding any number from 1 to n.

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 n = 4 to understand how the solution works.

We need to find the minimum money to guarantee finding any number from 1 to 4.

Step 1: Initialize DP table Create a 5×5 table f[i][j] (indices 0-4). All single number ranges have cost 0:

  • f[1][1] = f[2][2] = f[3][3] = f[4][4] = 0

Step 2: Fill ranges of size 2

For range [3, 4]:

  • We can guess 3: cost = 3 + max(f[3][2], f[4][4]) = 3 + max(0, 0) = 3
  • Initialize with worst case (guess 4): cost = 4 + f[3][3] = 4 + 0 = 4
  • Take minimum: f[3][4] = min(4, 3) = 3

For range [2, 3]:

  • We can guess 2: cost = 2 + max(f[2][1], f[3][3]) = 2 + max(0, 0) = 2
  • Initialize with worst case (guess 3): cost = 3 + f[2][2] = 3 + 0 = 3
  • Take minimum: f[2][3] = min(3, 2) = 2

For range [1, 2]:

  • We can guess 1: cost = 1 + max(f[1][0], f[2][2]) = 1 + max(0, 0) = 1
  • Initialize with worst case (guess 2): cost = 2 + f[1][1] = 2 + 0 = 2
  • Take minimum: f[1][2] = min(2, 1) = 1

Step 3: Fill ranges of size 3

For range [2, 4]:

  • Initialize with worst case (guess 4): cost = 4 + f[2][3] = 4 + 2 = 6
  • Try guess 2: cost = 2 + max(f[2][1], f[3][4]) = 2 + max(0, 3) = 5
  • Try guess 3: cost = 3 + max(f[2][2], f[4][4]) = 3 + max(0, 0) = 3
  • Take minimum: f[2][4] = min(6, 5, 3) = 3

For range [1, 3]:

  • Initialize with worst case (guess 3): cost = 3 + f[1][2] = 3 + 1 = 4
  • Try guess 1: cost = 1 + max(f[1][0], f[2][3]) = 1 + max(0, 2) = 3
  • Try guess 2: cost = 2 + max(f[1][1], f[3][3]) = 2 + max(0, 0) = 2
  • Take minimum: f[1][3] = min(4, 3, 2) = 2

Step 4: Fill range of size 4

For range [1, 4]:

  • Initialize with worst case (guess 4): cost = 4 + f[1][3] = 4 + 2 = 6
  • Try guess 1: cost = 1 + max(f[1][0], f[2][4]) = 1 + max(0, 3) = 4
  • Try guess 2: cost = 2 + max(f[1][1], f[3][4]) = 2 + max(0, 3) = 5
  • Try guess 3: cost = 3 + max(f[1][2], f[4][4]) = 3 + max(1, 0) = 4
  • Take minimum: f[1][4] = min(6, 4, 5, 4) = 4

Result: The minimum money needed to guarantee finding any number from 1 to 4 is f[1][4] = 4.

This means if we have $4, we can always find the secret number using the optimal strategy:

  • First guess 1 or 3 (both lead to cost 4 in worst case)
  • If we guess 1 and it's wrong, we need at most $3 more for range [2, 4]
  • If we guess 3 and it's wrong, we need at most $1 more for range [1, 2]
  • Total worst case: $4

Solution Implementation

1class Solution:
2    def getMoneyAmount(self, n: int) -> int:
3        # dp[i][j] represents the minimum amount of money needed to guarantee a win
4        # when guessing a number between i and j (inclusive)
5        dp = [[0] * (n + 1) for _ in range(n + 1)]
6      
7        # Process all ranges from smaller to larger
8        # Start from the second-to-last number and move backwards
9        for start in range(n - 1, 0, -1):
10            # For each starting position, consider all possible ending positions
11            for end in range(start + 1, n + 1):
12                # Initialize with the worst case: pick the last number (end - 1)
13                # If we pick j-1 and it's wrong, we only need to check range [i, j-1)
14                dp[start][end] = (end - 1) + dp[start][end - 1]
15              
16                # Try each possible guess k in the range [start, end)
17                for guess in range(start, end):
18                    # Cost for guessing k is:
19                    # k (the cost of guessing) + 
20                    # max of two scenarios (we prepare for the worst case):
21                    #   - if actual number < k: need dp[start][guess - 1]
22                    #   - if actual number > k: need dp[guess + 1][end]
23                    current_cost = guess + max(
24                        dp[start][guess - 1],  # Left subrange
25                        dp[guess + 1][end]      # Right subrange
26                    )
27                  
28                    # Keep track of the minimum cost among all possible guesses
29                    dp[start][end] = min(dp[start][end], current_cost)
30      
31        # Return the minimum money needed to guarantee winning for range [1, n]
32        return dp[1][n]
33
1class Solution {
2    public int getMoneyAmount(int n) {
3        // dp[i][j] represents the minimum amount of money needed to guarantee a win
4        // when guessing a number between i and j (inclusive)
5        int[][] dp = new int[n + 1][n + 1];
6      
7        // Iterate through all possible ranges, starting from smaller ranges to larger ones
8        // i represents the start of the range (moving from n-1 down to 1)
9        for (int start = n - 1; start > 0; --start) {
10            // j represents the end of the range (moving from start+1 to n)
11            for (int end = start + 1; end <= n; ++end) {
12                // Initialize with the worst case: guessing the rightmost number (end)
13                // If we guess end and it's wrong, we only need to check [start, end-1]
14                dp[start][end] = end + dp[start][end - 1];
15              
16                // Try all possible guesses k between start and end-1
17                // to find the minimum guaranteed cost
18                for (int guess = start; guess < end; ++guess) {
19                    // Cost for guessing k:
20                    // - We pay k for the guess
21                    // - If k is too small, we search in [guess+1, end]
22                    // - If k is too large, we search in [start, guess-1]
23                    // - We take the max of both sides (worst case scenario)
24                    int costForThisGuess = guess + Math.max(dp[start][guess - 1], dp[guess + 1][end]);
25                  
26                    // Update the minimum cost for range [start, end]
27                    dp[start][end] = Math.min(dp[start][end], costForThisGuess);
28                }
29            }
30        }
31      
32        // Return the minimum amount needed to guarantee a win for the range [1, n]
33        return dp[1][n];
34    }
35}
36
1class Solution {
2public:
3    int getMoneyAmount(int n) {
4        // dp[i][j] represents the minimum amount of money needed to guarantee a win
5        // when guessing a number between i and j (inclusive)
6        int dp[n + 1][n + 1];
7        memset(dp, 0, sizeof(dp));
8      
9        // Iterate through all possible ranges, starting from smaller ranges to larger ones
10        // i represents the start of the range (iterating from n-1 down to 1)
11        for (int start = n - 1; start >= 1; --start) {
12            // j represents the end of the range (iterating from start+1 to n)
13            for (int end = start + 1; end <= n; ++end) {
14                // Initialize with the worst case: guessing the largest number (end)
15                // If we guess end and it's wrong, we need to check range [start, end-1]
16                dp[start][end] = end + dp[start][end - 1];
17              
18                // Try each possible guess k between start and end-1
19                for (int guess = start; guess < end; ++guess) {
20                    // Cost for guessing k:
21                    // - We pay k dollars for the guess
22                    // - If k is too small, we check range [guess+1, end]
23                    // - If k is too large, we check range [start, guess-1]
24                    // - We take the max of both sides (worst case scenario)
25                    int costForGuess = guess + max(dp[start][guess - 1], dp[guess + 1][end]);
26                  
27                    // Update with the minimum cost among all possible guesses
28                    dp[start][end] = min(dp[start][end], costForGuess);
29                }
30            }
31        }
32      
33        // Return the minimum amount needed to guarantee a win for range [1, n]
34        return dp[1][n];
35    }
36};
37
1/**
2 * Calculates the minimum amount of money needed to guarantee a win in a guessing game.
3 * In this game, you guess a number between 1 and n, and pay the guessed number if wrong.
4 * The goal is to minimize the maximum possible loss.
5 * 
6 * @param n - The upper bound of the guessing range (1 to n)
7 * @returns The minimum amount of money needed to guarantee a win
8 */
9function getMoneyAmount(n: number): number {
10    // Create a 2D DP table where dp[i][j] represents the minimum cost 
11    // to guarantee a win when guessing between i and j
12    const dp: number[][] = Array.from(
13        { length: n + 1 }, 
14        () => Array(n + 1).fill(0)
15    );
16  
17    // Build the DP table from smaller ranges to larger ranges
18    // Start from the second last number and work backwards
19    for (let rangeStart = n - 1; rangeStart >= 1; rangeStart--) {
20        // For each starting position, consider all possible ending positions
21        for (let rangeEnd = rangeStart + 1; rangeEnd <= n; rangeEnd++) {
22            // Initialize with the worst case: guessing the largest number in range
23            dp[rangeStart][rangeEnd] = rangeEnd + dp[rangeStart][rangeEnd - 1];
24          
25            // Try each possible guess k within the current range
26            for (let guess = rangeStart; guess < rangeEnd; guess++) {
27                // For each guess, calculate the cost:
28                // - Pay the guessed number (guess)
29                // - Plus the maximum cost of the two possible subranges
30                //   (we prepare for the worst case scenario)
31                const currentCost = guess + Math.max(
32                    dp[rangeStart][guess - 1],  // Cost if actual number is less than guess
33                    dp[guess + 1][rangeEnd]      // Cost if actual number is greater than guess
34                );
35              
36                // Keep track of the minimum cost among all possible guesses
37                dp[rangeStart][rangeEnd] = Math.min(
38                    dp[rangeStart][rangeEnd], 
39                    currentCost
40                );
41            }
42        }
43    }
44  
45    // Return the minimum cost to guarantee a win for the full range [1, n]
46    return dp[1][n];
47}
48

Time and Space Complexity

Time Complexity: O(n³)

The code uses a dynamic programming approach with three nested loops:

  • The outer loop iterates from n-1 down to 1, giving us n-1 iterations
  • The middle loop iterates from i+1 to n, which in the worst case (when i=1) gives us approximately n iterations
  • The inner loop iterates from i to j-1, which in the worst case gives us approximately n iterations

Since we have three nested loops, each potentially iterating up to n times, the overall time complexity is O(n³).

Space Complexity: O(n²)

The code creates a 2D array f of size (n+1) × (n+1) to store the intermediate results of the dynamic programming solution. This requires O(n²) space. No other significant auxiliary space is used besides a few variables for loop counters, so the overall space complexity is O(n²).

Common Pitfalls

1. Incorrect DP Table Traversal Order

A critical mistake is processing the DP table in the wrong order. Many developers instinctively iterate from small indices to large indices (top-left to bottom-right), but this approach fails because it tries to use subproblem solutions before they're computed.

Wrong approach:

for start in range(1, n + 1):
    for end in range(start + 1, n + 1):
        # This fails because dp[guess + 1][end] hasn't been computed yet

Correct approach:

for start in range(n - 1, 0, -1):
    for end in range(start + 1, n + 1):
        # Now dp[guess + 1][end] is already computed

2. Misunderstanding the Problem Objective

Many interpret this as finding the average or best-case cost, but the problem requires the worst-case guarantee. When choosing a guess k, you must prepare for whichever scenario costs more: max(dp[start][k-1], dp[k+1][end]), not min() or average.

Wrong interpretation:

# Taking minimum of left and right (optimistic approach)
current_cost = guess + min(dp[start][guess - 1], dp[guess + 1][end])

Correct interpretation:

# Taking maximum of left and right (preparing for worst case)
current_cost = guess + max(dp[start][guess - 1], dp[guess + 1][end])

3. Off-by-One Errors in Range Boundaries

Confusion about inclusive vs exclusive ranges leads to bugs:

Common mistakes:

  • Using range(start, end + 1) for guesses (includes end, which shouldn't be guessed)
  • Accessing dp[start][guess] instead of dp[start][guess - 1]
  • Initializing with dp[start][end] = end + dp[start][end - 1] (should be end - 1)

Correct boundary handling:

for guess in range(start, end):  # Exclusive of end
    current_cost = guess + max(
        dp[start][guess - 1],    # Left range: [start, guess-1]
        dp[guess + 1][end]        # Right range: [guess+1, end]
    )

4. Inefficient Initialization Strategy

Some solutions don't initialize dp[start][end] before the inner loop, leading to unnecessary comparisons or using infinity values.

Inefficient:

dp[start][end] = float('inf')
for guess in range(start, end):
    # Every guess needs comparison with infinity

Efficient:

dp[start][end] = (end - 1) + dp[start][end - 1]  # Valid upper bound
for guess in range(start, end):
    # Can skip the last guess since it's already considered

5. Memory Optimization Oversight

While the 2D array solution works, some fail to recognize that only half the table is used (where start < end), wasting memory on unused cells.

Space-conscious approach: Consider using a dictionary for sparse storage or recognizing that dp[i][j] where i >= j is always 0 and doesn't need explicit storage.

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

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More