171 lines
7.4 KiB
YAML
171 lines
7.4 KiB
YAML
title: Guess Number Higher or Lower
|
|
slug: guess-number-higher-or-lower
|
|
difficulty: easy
|
|
leetcode_id: 374
|
|
leetcode_url: https://leetcode.com/problems/guess-number-higher-or-lower/
|
|
categories:
|
|
- binary-search
|
|
patterns:
|
|
- binary-search
|
|
|
|
description: |
|
|
We are playing the Guess Game. The game is as follows:
|
|
|
|
I pick a number from `1` to `n`. You have to guess which number I picked (the number I picked stays the same throughout the game).
|
|
|
|
Every time you guess wrong, I will tell you whether the number I picked is higher or lower than your guess.
|
|
|
|
You call a pre-defined API `int guess(int num)`, which returns three possible results:
|
|
|
|
- `-1`: Your guess is higher than the number I picked (i.e. `num > pick`).
|
|
- `1`: Your guess is lower than the number I picked (i.e. `num < pick`).
|
|
- `0`: Your guess is equal to the number I picked (i.e. `num == pick`).
|
|
|
|
Return *the number that I picked*.
|
|
|
|
constraints: |
|
|
- `1 <= n <= 2^31 - 1`
|
|
- `1 <= pick <= n`
|
|
|
|
examples:
|
|
- input: "n = 10, pick = 6"
|
|
output: "6"
|
|
explanation: "Using binary search, we narrow down the range until we find 6."
|
|
- input: "n = 1, pick = 1"
|
|
output: "1"
|
|
explanation: "There's only one number, so the answer is 1."
|
|
- input: "n = 2, pick = 1"
|
|
output: "1"
|
|
explanation: "We guess the middle (or lower), and the API tells us we found it or to go lower."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine playing a number guessing game with a friend. They're thinking of a number between 1 and 100, and after each guess, they tell you "higher" or "lower". What's the smartest strategy?
|
|
|
|
The optimal approach is to **always guess the middle of the remaining range**. If they say "higher", you eliminate all numbers below your guess. If they say "lower", you eliminate all numbers above. Each guess cuts the search space in half.
|
|
|
|
Think of it like searching for a word in a dictionary. You don't start from page 1 and check every page — you open to the middle, see if your word comes before or after, and repeat. This is the essence of **binary search**.
|
|
|
|
The key insight is that the API feedback (`-1`, `0`, `1`) directly tells us which half of the range to keep searching. We're guaranteed to find the answer because we systematically narrow down until only one number remains.
|
|
|
|
approach: |
|
|
We solve this using **Binary Search**:
|
|
|
|
**Step 1: Initialise the search boundaries**
|
|
|
|
- `low`: Set to `1` (the smallest possible number)
|
|
- `high`: Set to `n` (the largest possible number)
|
|
|
|
|
|
|
|
**Step 2: Binary search loop**
|
|
|
|
- While `low <= high`, calculate the middle point: `mid = low + (high - low) // 2`
|
|
- Call `guess(mid)` to get feedback
|
|
- If result is `0`: We found the number — return `mid`
|
|
- If result is `-1`: Our guess is too high — search in the lower half by setting `high = mid - 1`
|
|
- If result is `1`: Our guess is too low — search in the upper half by setting `low = mid + 1`
|
|
|
|
|
|
|
|
**Step 3: Return the result**
|
|
|
|
- The loop is guaranteed to find the answer since `1 <= pick <= n`
|
|
|
|
|
|
|
|
Note: We use `mid = low + (high - low) // 2` instead of `(low + high) // 2` to avoid integer overflow when `low + high` exceeds the maximum integer value.
|
|
|
|
common_pitfalls:
|
|
- title: Integer Overflow in Midpoint Calculation
|
|
description: |
|
|
A classic bug is calculating the midpoint as `(low + high) // 2`. When `low` and `high` are both large (close to `2^31 - 1`), their sum overflows.
|
|
|
|
For example, if `low = 2^30` and `high = 2^31 - 1`, then `low + high` exceeds the 32-bit signed integer limit, causing incorrect behavior or runtime errors.
|
|
|
|
Always use `low + (high - low) // 2` to safely compute the midpoint.
|
|
wrong_approach: "(low + high) // 2"
|
|
correct_approach: "low + (high - low) // 2"
|
|
|
|
- title: Linear Search
|
|
description: |
|
|
Guessing numbers one by one from `1` to `n` works but is extremely inefficient. With `n` up to `2^31 - 1` (over 2 billion), a linear approach could require billions of guesses.
|
|
|
|
Binary search guarantees finding the answer in at most `log2(n)` guesses — about 31 guesses for the maximum `n`.
|
|
wrong_approach: "Loop from 1 to n, calling guess(i)"
|
|
correct_approach: "Binary search halving the range each time"
|
|
|
|
- title: Off-by-One Errors
|
|
description: |
|
|
When updating `low` and `high`, you must exclude the middle element since we already checked it:
|
|
|
|
- When `guess(mid)` returns `-1` (guess too high), set `high = mid - 1`, not `high = mid`
|
|
- When `guess(mid)` returns `1` (guess too low), set `low = mid + 1`, not `low = mid`
|
|
|
|
Using `mid` instead of `mid - 1` or `mid + 1` can cause infinite loops.
|
|
|
|
key_takeaways:
|
|
- "**Binary search foundation**: This problem teaches the core binary search template — divide the search space in half based on a condition"
|
|
- "**Overflow prevention**: Always use `low + (high - low) // 2` for midpoint calculation in production code"
|
|
- "**Interactive problems**: Problems with API calls follow the same patterns — the API response guides which half to search"
|
|
- "**Logarithmic efficiency**: Binary search reduces `O(n)` to `O(log n)`, essential for large input ranges"
|
|
|
|
time_complexity: "O(log n). Each guess eliminates half of the remaining candidates, so we need at most log2(n) guesses."
|
|
space_complexity: "O(1). We only use three variables (`low`, `high`, `mid`) regardless of the input size."
|
|
|
|
solutions:
|
|
- approach_name: Binary Search
|
|
is_optimal: true
|
|
code: |
|
|
# The guess API is already defined for you.
|
|
# @param num: your guess
|
|
# @return -1 if num is higher than the picked number
|
|
# 1 if num is lower than the picked number
|
|
# otherwise return 0
|
|
# def guess(num: int) -> int:
|
|
|
|
def guess_number(n: int) -> int:
|
|
low, high = 1, n
|
|
|
|
while low <= high:
|
|
# Safe midpoint calculation to avoid overflow
|
|
mid = low + (high - low) // 2
|
|
|
|
result = guess(mid)
|
|
|
|
if result == 0:
|
|
# Found the number
|
|
return mid
|
|
elif result == -1:
|
|
# Guess is too high, search lower half
|
|
high = mid - 1
|
|
else:
|
|
# Guess is too low, search upper half
|
|
low = mid + 1
|
|
|
|
# Should never reach here given problem constraints
|
|
return -1
|
|
explanation: |
|
|
**Time Complexity:** O(log n) — Each iteration halves the search space.
|
|
|
|
**Space Complexity:** O(1) — Only three integer variables used.
|
|
|
|
This is the classic binary search template. We maintain a range `[low, high]` and repeatedly guess the middle value. Based on the API response, we eliminate half the range until we find the target.
|
|
|
|
- approach_name: Linear Search
|
|
is_optimal: false
|
|
code: |
|
|
def guess_number(n: int) -> int:
|
|
# Check each number from 1 to n
|
|
for i in range(1, n + 1):
|
|
if guess(i) == 0:
|
|
return i
|
|
|
|
return -1
|
|
explanation: |
|
|
**Time Complexity:** O(n) — In the worst case, we check every number.
|
|
|
|
**Space Complexity:** O(1) — Only loop variable used.
|
|
|
|
This brute force approach checks numbers sequentially. While correct, it's far too slow for large `n` (up to 2 billion). With `n = 2^31 - 1`, this could require over 2 billion API calls, causing Time Limit Exceeded. Included to illustrate why binary search is necessary.
|