374. Guess Number Higher or Lower
Problem Description
This is a classic number guessing game where you need to find a secret number between 1 and n.
The game works as follows:
- A number between 1 and n (inclusive) has been picked beforehand
- You need to find this number by making guesses
- For each guess you make, you'll receive feedback through a pre-defined API function
guess(num)
The guess(num) function returns:
-1if your guess is too high (your guess > the picked number)1if your guess is too low (your guess < the picked number)0if your guess is correct (your guess = the picked number)
Your task is to implement a function that finds and returns the picked number using the guess API.
The solution uses binary search to efficiently find the target number. By leveraging Python's bisect module with a custom key function, it searches for the first position where -guess(x) returns 0 or greater. The key function -guess(x) transforms the problem into finding the insertion point in a sorted sequence, where the negation converts the guess API's return values to work with bisect's expectations.
Intuition
When we need to find a specific number in a sorted range from 1 to n, and we can only check if our guess is too high, too low, or correct, binary search naturally comes to mind. This is because we can eliminate half of the remaining possibilities with each guess.
The traditional binary search approach would involve maintaining left and right pointers, calculating the middle point, and adjusting the search range based on the feedback from guess(). However, Python's bisect module provides a clever alternative.
The key insight is recognizing that bisect is designed to find insertion points in sorted sequences. If we can transform our guessing problem into finding an insertion point, we can leverage this built-in functionality.
Here's how the transformation works:
bisectexpects a sorted sequence where it needs to find where to insert a value- We want to find where
guess(x)transitions from returning 1 (too low) to -1 (too high), with 0 (correct) being our target - By negating the guess function with
-guess(x), we transform the return values:- When guess is too low (returns 1),
-guess(x)returns -1 - When guess is correct (returns 0),
-guess(x)returns 0 - When guess is too high (returns -1),
-guess(x)returns 1
- When guess is too low (returns 1),
This creates a sequence that goes from negative to positive values, and bisect searching for 0 will find exactly where this transition happens - which is our answer. The beauty of this approach is that it reduces the entire binary search logic to a single line using Python's optimized built-in function.
Solution Implementation
1# The guess API is already defined for you. 2# @param num, your guess 3# @return -1 if num is higher than the picked number 4# 1 if num is lower than the picked number 5# otherwise return 0 6# def guess(num: int) -> int: 7 8 9class Solution: 10 def guessNumber(self, n: int) -> int: 11 """ 12 Find the picked number between 1 and n using binary search. 13 14 Feasible condition: guess(mid) <= 0 means mid >= picked number. 15 We find the first value where guess(mid) <= 0 (i.e., mid is >= picked). 16 17 Args: 18 n: The upper bound of the range [1, n] 19 20 Returns: 21 The picked number 22 """ 23 # Define feasible: guess(mid) <= 0 means mid >= picked 24 # We want the first mid where this is true 25 def feasible(mid: int) -> bool: 26 return guess(mid) <= 0 27 28 left, right = 1, n 29 first_true_index = -1 30 31 while left <= right: 32 mid = (left + right) // 2 33 if feasible(mid): 34 first_true_index = mid 35 right = mid - 1 36 else: 37 left = mid + 1 38 39 return first_true_index 401/** 2 * Forward declaration of guess API. 3 * @param num your guess 4 * @return -1 if num is higher than the picked number 5 * 1 if num is lower than the picked number 6 * otherwise return 0 7 * int guess(int num); 8 */ 9 10public class Solution extends GuessGame { 11 /** 12 * Finds the picked number using the binary search template. 13 * Feasible condition: guess(mid) <= 0 means mid >= picked number. 14 * We find the first value where this is true. 15 */ 16 public int guessNumber(int n) { 17 int left = 1; 18 int right = n; 19 int firstTrueIndex = -1; 20 21 while (left <= right) { 22 int mid = left + (right - left) / 2; 23 if (guess(mid) <= 0) { 24 firstTrueIndex = mid; 25 right = mid - 1; 26 } else { 27 left = mid + 1; 28 } 29 } 30 31 return firstTrueIndex; 32 } 33} 341/** 2 * Forward declaration of guess API. 3 * @param num your guess 4 * @return -1 if num is higher than the picked number 5 * 1 if num is lower than the picked number 6 * otherwise return 0 7 * int guess(int num); 8 */ 9 10class Solution { 11public: 12 /** 13 * Finds the picked number using the binary search template. 14 * Feasible condition: guess(mid) <= 0 means mid >= picked number. 15 * We find the first value where this is true. 16 */ 17 int guessNumber(int n) { 18 int left = 1; 19 int right = n; 20 int firstTrueIndex = -1; 21 22 while (left <= right) { 23 int mid = left + (right - left) / 2; 24 if (guess(mid) <= 0) { 25 firstTrueIndex = mid; 26 right = mid - 1; 27 } else { 28 left = mid + 1; 29 } 30 } 31 32 return firstTrueIndex; 33 } 34}; 351/** 2 * Forward declaration of guess API. 3 * @param {number} num your guess 4 * @return -1 if num is higher than the picked number 5 * 1 if num is lower than the picked number 6 * otherwise return 0 7 * var guess = function(num) {} 8 */ 9 10/** 11 * Finds the picked number using the binary search template. 12 * Feasible condition: guess(mid) <= 0 means mid >= picked number. 13 * We find the first value where this is true. 14 * 15 * Time Complexity: O(log n) 16 * Space Complexity: O(1) 17 */ 18function guessNumber(n: number): number { 19 let left = 1; 20 let right = n; 21 let firstTrueIndex = -1; 22 23 while (left <= right) { 24 const mid = Math.floor((left + right) / 2); 25 if (guess(mid) <= 0) { 26 firstTrueIndex = mid; 27 right = mid - 1; 28 } else { 29 left = mid + 1; 30 } 31 } 32 33 return firstTrueIndex; 34} 35Solution Approach
The solution implements binary search using Python's bisect.bisect() function with a custom key function.
Implementation Details:
-
Range Setup: We create a range from 1 to n+1 using
range(1, n + 1), which represents all possible numbers we need to search through. -
Using bisect.bisect(): The
bisect.bisect()function performs binary search on the range. It takes three main parameters:- The sequence to search:
range(1, n + 1) - The value to search for:
0 - A key function:
lambda x: -guess(x)
- The sequence to search:
-
Key Function Transformation: The key function
lambda x: -guess(x)is crucial. For each numberxin the range:- If
xis less than the picked number,guess(x)returns 1, so-guess(x)returns -1 - If
xequals the picked number,guess(x)returns 0, so-guess(x)returns 0 - If
xis greater than the picked number,guess(x)returns -1, so-guess(x)returns 1
- If
-
How bisect Works: The
bisect.bisect()function searches for the insertion point of value0in the transformed sequence. Since the negated guess values create a sequence that goes from -1 (for numbers below target) to 0 (at target) to 1 (above target), bisect will find the exact position where 0 appears, which is our answer. -
Return Value: The function directly returns the result of
bisect.bisect(), which is the index (1-based in this case due to our range starting at 1) where the picked number is located.
Time Complexity: O(log n) - Binary search divides the search space in half with each iteration.
Space Complexity: O(1) - The range object is a generator that doesn't store all values in memory, and we only use constant extra space for the binary search operation.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example where n = 10 and the picked number is 6.
Initial Setup:
- Search range:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] - Target: Find the picked number (which is 6, but we don't know this yet)
- We'll use
bisect.bisect(range(1, 11), 0, key=lambda x: -guess(x))
How Binary Search Proceeds:
Step 1: Bisect starts by checking the middle of the range
- Middle position:
x = 5 - Call
guess(5): returns1(5 is too low, since picked = 6) - Key function:
-guess(5) = -1 - Since
-1 < 0, bisect knows the answer is in the upper half
Step 2: Search narrows to [6, 7, 8, 9, 10]
- Middle position:
x = 8 - Call
guess(8): returns-1(8 is too high, since picked = 6) - Key function:
-guess(8) = 1 - Since
1 > 0, bisect knows the answer is in the lower half
Step 3: Search narrows to [6, 7]
- Check position:
x = 6 - Call
guess(6): returns0(correct!) - Key function:
-guess(6) = 0 - Since we found exactly
0, this is our insertion point
Step 4: Bisect continues to verify the exact insertion position
- Check position:
x = 7 - Call
guess(7): returns-1(too high) - Key function:
-guess(7) = 1 - This confirms that
6is the last position where the key function returns≤ 0
Result: bisect.bisect() returns 6, which is the correct answer.
Visualizing the Transformation:
Number x: 1 2 3 4 5 6 7 8 9 10 guess(x): 1 1 1 1 1 0 -1 -1 -1 -1 -guess(x): -1 -1 -1 -1 -1 0 1 1 1 1 ↑ ↑ all negative target (returns 0)
The bisect function finds where to insert 0 in this transformed sequence, which is exactly at position 6 - our answer!
Time and Space Complexity
The time complexity is O(log n), where n is the upper limit given in the problem. This is because bisect.bisect performs a binary search on the range [1, n]. Binary search works by repeatedly dividing the search space in half, requiring at most log₂(n) comparisons to find the target element. Even though the code creates a range object and uses a lambda function as a key, the bisect module intelligently performs binary search without materializing the entire range, making only O(log n) calls to the guess function.
The space complexity is O(1). While the code creates a range(1, n + 1) object, Python's range objects are lazy iterators that don't store all values in memory - they only store the start, stop, and step values. The bisect.bisect function uses constant extra space for its binary search implementation (just a few variables to track indices). The lambda function and its execution also use constant space.
Common Pitfalls
Pitfall 1: Using Wrong Binary Search Template Variant
The Problem: Using while left < right with right = mid instead of the standard template with while left <= right and right = mid - 1.
Wrong Implementation:
while left < right: mid = (left + right) // 2 if guess(mid) <= 0: right = mid # WRONG - should be mid - 1 else: left = mid + 1 return left # WRONG - should track first_true_index
Solution: Use the standard template with first_true_index to track the answer:
first_true_index = -1 while left <= right: mid = (left + right) // 2 if guess(mid) <= 0: first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
Pitfall 2: Misunderstanding the Guess API Return Values
The guess() function returns:
-1if your guess is higher than the picked number1if your guess is lower than the picked number0if your guess is correct
Wrong Interpretation:
# WRONG: Inverting the meaning of -1 and 1 if guess(mid) == 1: # Thinking 1 means "too high" right = mid - 1
Solution: The feasible condition is guess(mid) <= 0, which means mid is greater than or equal to the picked number.
Pitfall 3: Integer Overflow in Mid Calculation
In languages with fixed integer sizes, (left + right) / 2 can overflow.
Wrong Implementation:
int mid = (left + right) / 2; // Can overflow when left + right > Integer.MAX_VALUE
Solution: Use left + (right - left) / 2 to avoid overflow:
int mid = left + (right - left) / 2;
Pitfall 4: Off-by-One Errors with Search Range
Using incorrect initial boundaries can miss the answer.
Wrong Implementation:
left, right = 0, n # WRONG: 0 is not a valid guess # or left, right = 1, n - 1 # WRONG: might miss n
Solution: Always use left = 1 and right = n since the picked number is between 1 and n inclusive.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!