704. Binary Search - Explanation
Description
You are given an array of distinct integers nums, sorted in ascending order, and an integer target.
Implement a function to search for target within nums. If it exists, then return its index, otherwise, return -1.
Your solution must run in time.
Example 1:
Input: nums = [-1,0,2,4,6,8], target = 4
Output: 3Example 2:
Input: nums = [-1,0,2,4,6,8], target = 3
Output: -1Constraints:
1 <= nums.length <= 10000.-10000 < nums[i], target < 10000- All the integers in
numsare unique.
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(logn) time and O(1) space, where n is the size of the input array.
Hint 1
Can you find an algorithm that is useful when the array is sorted? Maybe other than linear seacrh.
Hint 2
The problem name is the name of the algorithm that we can use. We need to find a target value and if it does not exist in the array return -1. We have l and r as the boundaries of the segment of the array in which we are searching. Try building conditions to eliminate half of the search segment at each step. Maybe sorted nature of the array can be helpful.
Hint 3
We compare the target value with the mid of the segment. For example, consider the array [1, 2, 3, 4, 5] and target = 4. The mid value is 3, thus, on the next iteration we search to the right of mid. The remaining segment is [4,5]. Why?
Hint 4
Because the array is sorted, all elements to the left of mid (including 3) are guaranteed to be smaller than the target. Therefore, we can safely eliminate that half of the array from consideration, narrowing the search to the right half and repeat this search until we find the target.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Arrays - Understanding how arrays are indexed and accessed
- Sorted Arrays - Recognizing properties of sorted data that enable efficient searching
- Recursion - Writing and understanding recursive functions with base cases
- Time Complexity - Understanding O(log n) vs O(n) and why halving the search space is efficient
1. Recursive Binary Search
Intuition
Binary search works by repeatedly cutting the search space in half.
Instead of scanning the entire array, we check the middle element:
- If it’s the target → return the index.
- If the target is larger → search only in the right half.
- If the target is smaller → search only in the left half.
The recursive version simply expresses this idea as a function that keeps calling itself on the appropriate half until the target is found or the range becomes invalid.
Algorithm
- Define a recursive function that takes the current search range
[l, r]. - If
l > r, the range is empty, return-1. - Compute the middle index
m = (l + r) // 2. - Compare
nums[m]withtarget:- If equal, return
m. - If
nums[m] < target, recursively search[m + 1, r]. - If
nums[m] > target, recursively search[l, m - 1].
- If equal, return
- Start the recursion with the full range
[0, n - 1]. - Return the final result.
class Solution:
def binary_search(self, l: int, r: int, nums: List[int], target: int) -> int:
if l > r:
return -1
m = l + (r - l) // 2
if nums[m] == target:
return m
if nums[m] < target:
return self.binary_search(m + 1, r, nums, target)
return self.binary_search(l, m - 1, nums, target)
def search(self, nums: List[int], target: int) -> int:
return self.binary_search(0, len(nums) - 1, nums, target)Time & Space Complexity
- Time complexity:
- Space complexity:
2. Iterative Binary Search
Intuition
Binary search checks the middle element of a sorted array and decides which half to discard.
Instead of using recursion, the iterative approach keeps shrinking the search range using a loop.
We adjust the left and right pointers until we either find the target or the pointers cross, meaning the target isn’t present.
Algorithm
- Initialize two pointers:
l = 0(start of array)r = len(nums) - 1(end of array)
- While
l <= r:- Compute
m = l + (r - l) // 2(safe midpoint). - If
nums[m] == target, returnm. - If
nums[m] < target, move search to the right half: updatel = m + 1. - If
nums[m] > target, move search to the left half: updater = m - 1.
- Compute
- If the loop ends without finding the target, return
-1.
Time & Space Complexity
- Time complexity:
- Space complexity:
3. Upper Bound
Intuition
Upper bound binary search finds the first index where a value greater than the target appears.
Once we know that position, the actual target—if it exists—must be right before it.
So instead of directly searching for the target, we search for the boundary where values stop being ≤ target.
Then we simply check whether the element just before that boundary is the target.
Algorithm
- Set
l = 0andr = len(nums)(right is one past the last index). - While
l < r:- Compute midpoint
m. - If
nums[m] > target, shrink the right side:r = m. - Otherwise (
nums[m] <= target), shrink the left side:l = m + 1.
- Compute midpoint
- After the loop:
lis the upper bound: first index wherenums[l] > target.- So the potential location of the target is
l - 1.
- If
l > 0andnums[l - 1] == target, returnl - 1. - Otherwise, return
-1(target not found).
Time & Space Complexity
- Time complexity:
- Space complexity:
4. Lower Bound
Intuition
Lower bound binary search finds the first index where a value is greater than or equal to the target.
This means if the target exists in the array, this lower-bound index will point exactly to its first occurrence.
So instead of directly searching for equality, we search for the leftmost position where the target could appear, then verify it.
This approach is especially useful for sorted arrays because it avoids overshooting and naturally handles duplicates.
Algorithm
- Initialize:
l = 0r = len(nums)(right is one past the last index).
- While
l < r:- Compute midpoint
m. - If
nums[m] >= target, shrink the search to the left half:r = m. - Otherwise (
nums[m] < target), search in the right half:l = m + 1.
- Compute midpoint
- After the loop:
lis the lower bound: first index where value >=target.
- If
lis within bounds andnums[l] == target, returnl. - Otherwise, return
-1(the target is not in the array).
Time & Space Complexity
- Time complexity:
- Space complexity:
5. Built-In Function
Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Integer Overflow When Calculating Mid
Using (l + r) / 2 can overflow when l and r are large. Use l + (r - l) / 2 instead to safely compute the midpoint.
# Wrong: can overflow in some languages
m = (l + r) // 2
# Correct: prevents overflow
m = l + (r - l) // 2Infinite Loop Due to Wrong Pointer Update
Updating l = m instead of l = m + 1 (or r = m instead of r = m - 1 in some variants) can cause an infinite loop when l and r are adjacent.
Off-by-One Errors with Loop Condition
Using while l <= r vs while l < r changes the behavior significantly. Mixing these up with the wrong pointer updates causes bugs. Be consistent with your chosen template.
Not Checking if Target Was Actually Found
Binary search converges to a position, but that position might not contain the target. Always verify that nums[result] == target before returning the index.
Sign in to join the discussion