The problem
You get an array nums of distinct integers sorted in ascending order, and an integer target. Return the index of target in nums, or -1 if it isn’t there. Your algorithm must run in O(log n) time.
Input: nums = [-1, 0, 3, 5, 9, 12], target = 9
Output: 49 sits at index 4.
How to think about it
Start with brute force. Scan left to right and return the index when you see target. That is O(n), and it ignores the one thing the problem hands you: the array is sorted.
What does “sorted” buy you? Look at any single element nums[mid]. If it is smaller than the target, then every element to its left is smaller too, so the target can only be to the right. If it is bigger, the target can only be to the left. One comparison throws away half of what’s left.
The key insight: keep a range that must contain the target, and halve it. Hold lo and hi so that, if the target exists, it is somewhere in nums[lo..hi] (both ends included). Check the middle. Either you found it, or you move lo past mid or hi before mid. When lo > hi, the range is empty and the target isn’t there. Halving from n down to 0 takes about log₂ n steps, so a million elements need about 20 comparisons.
Which pattern is this? Binary search, in its purest form. The tell is a sorted input plus a requirement of O(log n). Learn this closed-interval template exactly: while lo <= hi, lo = mid + 1, hi = mid - 1. Most bugs come from mixing it with other templates.
Approach
- Set
lo = 0andhi = len(nums) - 1. - While
lo <= hi:- Let
mid = (lo + hi) // 2. - If
nums[mid] == target, returnmid. - If
nums[mid] < target, the target is to the right:lo = mid + 1. - Otherwise it is to the left:
hi = mid - 1.
- Let
- The range is empty, so return
-1.
Solution
def binary_search(nums, target):
lo, hi = 0, len(nums) - 1 # target, if present, is in nums[lo..hi]
while lo <= hi: # range is non-empty
mid = (lo + hi) // 2
if nums[mid] == target: return mid
if nums[mid] < target: lo = mid + 1 # discard mid and everything left
else: hi = mid - 1 # discard mid and everything right
return -1
# binary_search([-1,0,3,5,9,12], 9) -> 4Dry run
On nums = [-1, 0, 3, 5, 9, 12], target = 9:
| Step | lo |
hi |
mid |
nums[mid] |
Decision |
|---|---|---|---|---|---|
| 1 | 0 | 5 | 2 | 3 | 3 < 9, so lo = 3 |
| 2 | 3 | 5 | 4 | 9 | found, return 4 |
Two comparisons instead of five, and the answer is 4.
Complexity
- Time: O(log n). Each iteration at least halves the size of
[lo, hi]. - Space: O(1). Just three integers.
Edge cases and pitfalls
- Empty array:
hi = -1, the loop never runs, and you return-1. <instead of<=in the loop condition skips the last one-element range, so a target at the final position checked is missed.lo = midorhi = midin this template can loop forever whenlo == hi. With the closed interval, always step pastmid.- Overflow: in Java or C++,
(lo + hi) / 2can overflow; writelo + (hi - lo) / 2. Python integers don’t overflow, but interviewers like hearing that you know this. - Target smaller than everything or bigger than everything both end with an empty range and return
-1.
Related problems
- Search Insert Position (LeetCode 35): same search, but return where the target would go.
- Search a 2D Matrix (LeetCode 74): this exact template on a matrix flattened into one index range.
- Search in Rotated Sorted Array (LeetCode 33): the same loop when only half the array is sorted at each step.
- 1.Binary Search (LeetCode 704)
- 2.Search Insert Position (LeetCode 35)
- 3.First Bad Version (LeetCode 278)
- 4.Search in Rotated Sorted Array (LeetCode 33)
- 5.Find Minimum in Rotated Sorted Array (LeetCode 153)
- 6.Find First and Last Position of Element in Sorted Array (LeetCode 34)
- 7.Search a 2D Matrix (LeetCode 74)
- 8.Find Peak Element (LeetCode 162)
- 9.Koko Eating Bananas (LeetCode 875)
- 10.Capacity To Ship Packages Within D Days (LeetCode 1011)
- 11.Time Based Key-Value Store (LeetCode 981)
- 12.Split Array Largest Sum (LeetCode 410)
- 13.Median of Two Sorted Arrays (LeetCode 4)