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:
- A secret number is picked between 1 and n (inclusive)
- You make guesses to find the number
- After each wrong guess, you're told if the secret number is higher or lower than your guess
- Each wrong guess costs you money equal to the number you guessed (e.g., guessing 5 costs $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.
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:
- We guess correctly - cost is 0
- The number is lower - we pay
k
and continue in range[i, k-1]
- 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:
- Try every possible guess
k
in that range - For each guess, calculate the worst-case cost:
k + max(f[i][k-1], f[k+1][j])
- 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 alli
(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
fromn-1
down to1
- Inner loop:
j
fromi+1
ton
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:
-
Initialize with the worst strategy:
f[i][j] = j + f[i][j-1]
(always guess the largest number) -
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])
- Cost of guessing
-
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 EvaluatorExample 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 to1
, giving usn-1
iterations - The middle loop iterates from
i+1
ton
, which in the worst case (wheni=1
) gives us approximatelyn
iterations - The inner loop iterates from
i
toj-1
, which in the worst case gives us approximatelyn
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 (includesend
, which shouldn't be guessed) - Accessing
dp[start][guess]
instead ofdp[start][guess - 1]
- Initializing with
dp[start][end] = end + dp[start][end - 1]
(should beend - 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.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!