704. Binary Search
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
targetif it exists in the array - Return
-1iftargetis 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]andtarget = 5, the function should return2(sincenums[2] = 5) - If
nums = [1, 3, 5, 7, 9]andtarget = 6, the function should return-1(since 6 is not in the array)
The key insight is that since the array is sorted, you can eliminate half of the search space in each step by comparing the middle element with the target, leading to the required logarithmic time complexity.
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 settingr = mid - If
nums[mid] < target, we know the target must be to the right, so we exclude mid by settingl = 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.
Learn more about Binary Search patterns.
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
221class 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}
361class 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};
291/**
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}
32Solution Approach
We implement binary search with two pointers to find the target in the sorted array.
Step-by-step implementation:
-
Initialize boundaries: Set
l = 0(left boundary) andr = len(nums) - 1(right boundary) to cover the entire array. -
Binary search loop: Continue searching while
l < r:- Calculate the middle position:
mid = (l + r) >> 1(equivalent to(l + r) // 2) - Compare
nums[mid]withtarget:- If
nums[mid] >= target: The target is either atmidor to its left, so we move the right boundary tomidby settingr = mid - If
nums[mid] < target: The target must be to the right ofmid, so we move the left boundary tomid + 1by settingl = mid + 1
- If
- Calculate the middle position:
-
Check result: When the loop ends (when
l == r), we have converged to a single position:- If
nums[l] == target, we found the target and returnl - Otherwise, the target doesn't exist in the array, so we return
-1
- If
Why this approach works:
The algorithm maintains an invariant that if the target exists in the array, it must be within the range [l, r]. By comparing with the middle element, we can safely eliminate half of the search space in each iteration.
The condition nums[mid] >= target ensures that we find the leftmost occurrence if there were duplicates (though this problem assumes unique elements). When l and r converge, l points to the smallest index where nums[index] >= target.
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
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample 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:
l = 0,r = 5, sol < r(continue)- Calculate
mid = (0 + 5) >> 1 = 2 - Check
nums[2] = 5 - Compare:
5 < 7, so target must be to the right - Update:
l = mid + 1 = 3 - New range: indices
[3, 5]→ values[7, 9, 11]
Iteration 2:
l = 3,r = 5, sol < r(continue)- Calculate
mid = (3 + 5) >> 1 = 4 - Check
nums[4] = 9 - Compare:
9 >= 7, so target is at mid or to its left - Update:
r = mid = 4 - New range: indices
[3, 4]→ values[7, 9]
Iteration 3:
l = 3,r = 4, sol < r(continue)- Calculate
mid = (3 + 4) >> 1 = 3 - Check
nums[3] = 7 - Compare:
7 >= 7, so target is at mid or to its left - Update:
r = mid = 3 - New range: index
[3]→ value[7]
Termination:
l = 3,r = 3, sol == r(exit loop)- Check:
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:
- After similar iterations, we converge to
l = r = 3 - Check:
nums[3] = 7, which is7 ≠ 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.
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 log₂(n) times until l and r converge.
The space complexity is O(1). The algorithm only uses a constant amount of extra space for the variables l, r, and mid, regardless of the input size. No additional data structures are created that scale with the input.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Empty Array Handling
The current implementation fails when the input array is empty. Accessing nums[left] when nums = [] will throw an IndexError.
Solution: Add an early check for empty arrays:
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:
- Keep
while left < rightwithright = mid(as in the current code) - Or use
while left <= rightwithright = mid - 1:
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
A heap is a ...?
Recommended Readings
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion If you prefer videos here's a video that explains recursion in a fun and easy way Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask him
Want a Structured Path to Master System Design Too? Don’t Miss This!