LeetCode 704: Binary Search
A clear explanation of searching for a target in a sorted array using binary search.
Problem Restatement
We are given an array of integers nums.
The array is sorted in ascending order.
We are also given an integer target.
We need to find target in nums.
If target exists, return its index.
If target does not exist, return:
-1
The required runtime is O(log n), so we should use binary search instead of scanning the array one element at a time.
Input and Output
| Item | Meaning |
|---|---|
| Input | A sorted integer array nums |
| Input | An integer target |
| Output | The index of target, or -1 if missing |
| Required complexity | O(log n) time |
| Array order | Ascending |
The function shape is:
class Solution:
def search(self, nums: list[int], target: int) -> int:
...
Examples
Example 1:
nums = [-1, 0, 3, 5, 9, 12]
target = 9
The value 9 appears at index 4.
So the answer is:
4
Example 2:
nums = [-1, 0, 3, 5, 9, 12]
target = 2
The value 2 does not appear in the array.
So the answer is:
-1
First Thought: Linear Search
The simplest solution is to check every element from left to right.
class Solution:
def search(self, nums: list[int], target: int) -> int:
for i, num in enumerate(nums):
if num == target:
return i
return -1
This is correct, but it does not satisfy the required runtime.
If the array has n elements, linear search can take:
O(n)
The problem asks for O(log n), so we need to use the sorted order.
Key Insight
Because the array is sorted, we can discard half of the remaining search space after each comparison.
Pick the middle index.
If the middle value equals target, we are done.
If the middle value is smaller than target, then every value on the left side is also too small. We only need to search the right half.
If the middle value is larger than target, then every value on the right side is also too large. We only need to search the left half.
This is binary search.
Algorithm
Use two pointers:
left = 0
right = len(nums) - 1
These pointers describe the current search range.
While left <= right:
- Compute the middle index.
- Compare
nums[mid]withtarget. - If they are equal, return
mid. - If
nums[mid] < target, movelefttomid + 1. - If
nums[mid] > target, moverighttomid - 1.
If the loop ends, the target is not in the array.
Return -1.
Correctness
At the start, the search range is the entire array.
During each iteration, the algorithm chooses the middle index mid.
If nums[mid] == target, returning mid is correct.
If nums[mid] < target, then every index from left through mid contains a value less than or equal to nums[mid], because the array is sorted. Therefore none of those values can be target. Removing them from the search range is safe.
If nums[mid] > target, then every index from mid through right contains a value greater than or equal to nums[mid]. Therefore none of those values can be target. Removing them from the search range is safe.
So after every step, if target exists, it remains inside the current search range.
When the range becomes empty, there is no possible index left where target can appear. Returning -1 is correct.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(log n) |
Each step cuts the search range roughly in half |
| Space | O(1) |
Only a few integer variables are used |
Implementation
class Solution:
def search(self, nums: list[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = left + (right - left) // 2
if nums[mid] == target:
return mid
if nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Code Explanation
We begin with the full array as the search range:
left = 0
right = len(nums) - 1
The loop continues while the range is valid:
while left <= right:
The middle index is:
mid = left + (right - left) // 2
This form avoids overflow in languages with fixed-size integers. In Python, overflow is not a practical issue, but this version is still a good habit.
If the middle value is the target, return the index:
if nums[mid] == target:
return mid
If the middle value is too small, the target can only be on the right:
if nums[mid] < target:
left = mid + 1
Otherwise, the middle value is too large, so the target can only be on the left:
else:
right = mid - 1
If the loop finishes, the target was not found:
return -1
Testing
def test_search():
s = Solution()
assert s.search([-1, 0, 3, 5, 9, 12], 9) == 4
assert s.search([-1, 0, 3, 5, 9, 12], 2) == -1
assert s.search([5], 5) == 0
assert s.search([5], -5) == -1
assert s.search([1, 2, 3, 4, 5], 1) == 0
assert s.search([1, 2, 3, 4, 5], 5) == 4
assert s.search([-10, -3, 0, 7, 11], -3) == 1
print("all tests passed")
test_search()
Test coverage:
| Test | Why |
|---|---|
| Target exists in middle | Confirms normal binary search |
| Target missing | Confirms -1 result |
| Single element found | Confirms minimum input |
| Single element missing | Confirms empty search range after one step |
| Target at first index | Confirms left boundary |
| Target at last index | Confirms right boundary |
| Negative values | Confirms comparisons work across signs |