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:
-1The 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 = 9The value 9 appears at index 4.
So the answer is:
4Example 2:
nums = [-1, 0, 3, 5, 9, 12]
target = 2The value 2 does not appear in the array.
So the answer is:
-1First 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 -1This 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) - 1These 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 -1Code Explanation
We begin with the full array as the search range:
left = 0
right = len(nums) - 1The loop continues while the range is valid:
while left <= right:The middle index is:
mid = left + (right - left) // 2This 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 midIf the middle value is too small, the target can only be on the right:
if nums[mid] < target:
left = mid + 1Otherwise, the middle value is too large, so the target can only be on the left:
else:
right = mid - 1If the loop finishes, the target was not found:
return -1Testing
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 |