Facebook Pixel

704. Binary Search

LeetCode ↗

Problem Description

You are given a sorted array of integers nums in ascending order and an integer target. Your task is to search for the target value in the array.

The function should:

  • Return the index of target if it exists in the array
  • Return -1 if target is not found in the array

Important constraint: Your algorithm must have O(log n) runtime complexity, which means you need to use binary search rather than a linear scan through the array.

For example:

  • If nums = [1, 3, 5, 7, 9] and target = 5, the function should return 2 (since nums[2] = 5)
  • If nums = [1, 3, 5, 7, 9] and target = 6, the function should return -1 (since 6 is not in the array)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

How We Pick the Algorithm

Why Binary Search?

This problem maps to Binary Search through a short path in the full flowchart.

Graph?noSorted ormonotonic?yesBinary Search

Standard binary search on the sorted array finds the first index whose value is at least the target.

Open in Flowchart

Intuition

Since we need O(log n) complexity and the array is sorted, binary search is the natural choice. The sorted property allows us to make intelligent decisions about which half of the array to search next.

The key idea is to repeatedly divide the search space in half. At each step, we look at the middle element and ask: "Is our target in the left half or the right half?" We can answer this question by comparing the middle element with our target.

The specific implementation uses a slightly different variant of binary search. Instead of having three cases (found, go left, go right), it uses a two-way decision:

  • If nums[mid] >= target, we include the middle element in our search space by setting r = mid
  • If nums[mid] < target, we know the target must be to the right, so we exclude mid by setting l = mid + 1

This approach guarantees that if the target exists, it will eventually be at position l when the loop terminates (when l equals r). The reason this works is that we're essentially finding the leftmost position where nums[pos] >= target. If the element at that position equals our target, we've found it; otherwise, the target doesn't exist in the array.

The bit shift operation (l + r) >> 1 is just an efficient way to calculate the middle point, equivalent to (l + r) // 2, avoiding potential integer overflow issues in other languages.

Pattern Learn more about Binary Search patterns.

New to patterns?Most interview problems reduce to a handful of patterns. This video covers the main eight.

Solution Approach

We implement binary search with two pointers to find the target in the sorted array.

Step-by-step implementation:

  1. Initialize boundaries: Set left = 0 and right = len(nums) - 1 to cover the entire array, and set first_true_index = -1 to record the best candidate found so far.

  2. Binary search loop: Continue searching while left <= right:

    • Calculate the middle position: mid = (left + right) // 2
    • Compare nums[mid] with target:
      • If nums[mid] >= target: mid is a candidate, so we record first_true_index = mid and keep looking to its left by setting right = mid - 1
      • If nums[mid] < target: The target must be to the right of mid, so we set left = mid + 1
  3. Check result: When the loop ends (when left > right), first_true_index holds the smallest index where nums[index] >= target, or -1 if every element is smaller than target:

    • If first_true_index != -1 and nums[first_true_index] == target, we found the target and return first_true_index
    • Otherwise, the target doesn't exist in the array, so we return -1

Why this approach works:

The array is split into a prefix where nums[index] < target and a suffix where nums[index] >= target. Each comparison tells us which side of this boundary mid is on, so we can safely eliminate half of the search space in each iteration while keeping the best candidate in first_true_index.

The condition nums[mid] >= target finds the leftmost index with a value at least target. If the target exists, it is exactly at that index (and this would be its leftmost occurrence if there were duplicates, though this problem has unique elements).

Time Complexity: O(log n) - We halve the search space in each iteration Space Complexity: O(1) - We only use a constant amount of extra space for the pointers

Example Walkthrough

Let's trace through the algorithm with nums = [1, 3, 5, 7, 9, 11] and target = 7.

Initial Setup:

  • l = 0, r = 5 (array indices)
  • Array: [1, 3, 5, 7, 9, 11]
  • Looking for: 7

Iteration 1:

  • left = 0, right = 5, so left <= right (continue)
  • Calculate mid = (0 + 5) // 2 = 2
  • Check nums[2] = 5
  • Compare: 5 < 7, so target must be to the right
  • Update: left = mid + 1 = 3
  • New range: indices [3, 5] → values [7, 9, 11]

Iteration 2:

  • left = 3, right = 5, so left <= right (continue)
  • Calculate mid = (3 + 5) // 2 = 4
  • Check nums[4] = 9
  • Compare: 9 >= 7, so record first_true_index = 4 and look to the left
  • Update: right = mid - 1 = 3
  • New range: index [3] → value [7]

Iteration 3:

  • left = 3, right = 3, so left <= right (continue)
  • Calculate mid = (3 + 3) // 2 = 3
  • Check nums[3] = 7
  • Compare: 7 >= 7, so record first_true_index = 3 and look to the left
  • Update: right = mid - 1 = 2

Termination:

  • left = 3, right = 2, so left > right (exit loop)
  • Check: nums[first_true_index] = nums[3] = 7, which equals our target
  • Return: 3

Example with target not found: Let's quickly see what happens with the same array but target = 6:

  • The comparisons are the same as above (5 < 6, 9 >= 6, 7 >= 6), so the loop ends with first_true_index = 3
  • Check: nums[3] = 7, which is 7 ≠ 6
  • Return: -1

The algorithm efficiently narrows down the search space by half in each iteration, examining only 3 elements out of 6 to find the target, demonstrating the logarithmic time complexity.

Solution Implementation

1class Solution:
2    def search(self, nums: List[int], target: int) -> int:
3        # Initialize left and right pointers for binary search
4        left, right = 0, len(nums) - 1
5        first_true_index = -1
6
7        # Binary search using the template: find first index where nums[mid] >= target
8        while left <= right:
9            mid = (left + right) // 2
10
11            if nums[mid] >= target:
12                first_true_index = mid
13                right = mid - 1
14            else:
15                left = mid + 1
16
17        # Check if the element at first_true_index equals target
18        # Return the index if found, otherwise return -1
19        if first_true_index != -1 and nums[first_true_index] == target:
20            return first_true_index
21        return -1
22
1class Solution {
2    /**
3     * Binary search to find the target element in a sorted array.
4     * Returns the index of the target if found, otherwise returns -1.
5     *
6     * @param nums   sorted array of integers
7     * @param target the value to search for
8     * @return index of target if found, -1 otherwise
9     */
10    public int search(int[] nums, int target) {
11        // Initialize left and right pointers for binary search
12        int left = 0;
13        int right = nums.length - 1;
14        int firstTrueIndex = -1;
15
16        // Binary search using the template: find first index where nums[mid] >= target
17        while (left <= right) {
18            int mid = left + (right - left) / 2;
19
20            if (nums[mid] >= target) {
21                firstTrueIndex = mid;
22                right = mid - 1;
23            } else {
24                left = mid + 1;
25            }
26        }
27
28        // Check if the element at firstTrueIndex equals target
29        // Return the index if found, otherwise return -1
30        if (firstTrueIndex != -1 && nums[firstTrueIndex] == target) {
31            return firstTrueIndex;
32        }
33        return -1;
34    }
35}
36
1class Solution {
2public:
3    int search(vector<int>& nums, int target) {
4        // Initialize left and right pointers for binary search
5        int left = 0;
6        int right = nums.size() - 1;
7        int firstTrueIndex = -1;
8
9        // Binary search using the template: find first index where nums[mid] >= target
10        while (left <= right) {
11            int mid = left + (right - left) / 2;
12
13            if (nums[mid] >= target) {
14                firstTrueIndex = mid;
15                right = mid - 1;
16            } else {
17                left = mid + 1;
18            }
19        }
20
21        // Check if the element at firstTrueIndex equals target
22        // Return the index if found, otherwise return -1
23        if (firstTrueIndex != -1 && nums[firstTrueIndex] == target) {
24            return firstTrueIndex;
25        }
26        return -1;
27    }
28};
29
1/**
2 * Binary search to find the target in a sorted array
3 * @param nums - Sorted array of numbers in ascending order
4 * @param target - The target value to search for
5 * @returns The index of target if found, otherwise -1
6 */
7function search(nums: number[], target: number): number {
8    // Initialize left and right pointers for binary search
9    let left: number = 0;
10    let right: number = nums.length - 1;
11    let firstTrueIndex: number = -1;
12
13    // Binary search using the template: find first index where nums[mid] >= target
14    while (left <= right) {
15        const mid: number = Math.floor((left + right) / 2);
16
17        if (nums[mid] >= target) {
18            firstTrueIndex = mid;
19            right = mid - 1;
20        } else {
21            left = mid + 1;
22        }
23    }
24
25    // Check if the element at firstTrueIndex equals target
26    // Return the index if found, otherwise return -1
27    if (firstTrueIndex !== -1 && nums[firstTrueIndex] === target) {
28        return firstTrueIndex;
29    }
30    return -1;
31}
32

Time and Space Complexity

The time complexity is O(log n), where n is the length of the array nums. This is because the algorithm uses binary search, which divides the search space in half with each iteration. In the worst case, the while loop runs about log₂(n) + 1 times until left passes right.

The space complexity is O(1). The algorithm only uses a constant amount of extra space for the variables left, right, mid, and first_true_index, regardless of the input size. No additional data structures are created that scale with the input.

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

Common Pitfalls

1. Empty Array Handling

A version that ends with return left if nums[left] == target else -1 (the while left < right template) throws an IndexError when nums = [], because nums[0] does not exist. The solution above does not have this problem: with an empty array, right = -1, the loop never runs, first_true_index stays -1, and the function returns -1. The problem's constraints also guarantee at least one element.

Solution: If you use a template that reads nums[left] after the loop, add an early check:

def search(self, nums: List[int], target: int) -> int:
    if not nums:  # Handle empty array
        return -1

    left, right = 0, len(nums) - 1
    # ... rest of the code

2. Integer Overflow in Middle Calculation

While Python handles large integers gracefully, in other languages like Java or C++, calculating mid = (left + right) // 2 can cause integer overflow when left + right exceeds the maximum integer value.

Solution: Use the overflow-safe formula:

mid = left + (right - left) // 2
# Or keep the bit shift version which is also safe:
mid = left + ((right - left) >> 1)

3. Incorrect Loop Condition Leading to Infinite Loop

A common mistake is using while left <= right with the update logic right = mid, which can cause an infinite loop when left == right and nums[mid] >= target.

Solution: Either:

  • Use while left < right with right = mid
  • Or use while left <= right with right = mid - 1 (as in the current code, which also records first_true_index), or the classic version that returns as soon as it finds the target:
while left <= right:
    mid = (left + right) >> 1
    if nums[mid] == target:
        return mid
    elif nums[mid] > target:
        right = mid - 1  # Note: mid - 1, not mid
    else:
        left = mid + 1

4. Confusing Binary Search Variants

The given implementation finds the leftmost position where nums[index] >= target. If the problem requires finding any occurrence (not necessarily the leftmost), the logic becomes simpler but different.

Solution for finding any occurrence:

def search(self, nums: List[int], target: int) -> int:
    left, right = 0, len(nums) - 1

    while left <= right:
        mid = (left + right) >> 1
        if nums[mid] == target:
            return mid  # Return immediately when found
        elif nums[mid] < target:
            left = mid + 1
        else:
            right = mid - 1

    return -1

5. Off-by-One Errors in Boundary Updates

Forgetting to use mid + 1 or mid - 1 appropriately can lead to missing elements or infinite loops. The rule of thumb:

  • When moving left boundary past mid: left = mid + 1
  • When keeping mid in search space: right = mid
  • When excluding mid from search space: right = mid - 1

Ready to land your dream job?

Unlock your dream job with a 5-minute quiz for a personalized study roadmap!

Get My Roadmap
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Get a Personalized Study Roadmap:

The three-steps of Depth First Search are:

  1. Identify states;
  2. Draw the state-space tree;
  3. DFS on the state-space tree.

Recommended Readings

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

Load More