NEETCODE - Binary Search: Binary Search
The Foundation of Logarithmic Search - From O(n) to O(log n)
Technology: Python, Algorithms
Skill: Binary Search, O(log n)
1. Problem Statement
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 O(log n) time.
Example 1:
Input: nums = [-1,0,2,4,6,8], target = 4 Output: 3
Example 2:
Input: nums = [-1,0,2,4,6,8], target = 3 Output: -1
Constraints:
1 <= nums.length <= 10000-10000 < nums[i], target < 10000- All integers in
numsare distinct numsis sorted in ascending order
Edge Cases to Consider:
- Single element array:
[5], target = 5 or target = 3 - Target is the first element
- Target is the last element
- Target is in the middle
- Target doesn't exist (smaller than all, larger than all, or in between)
- Array with two elements
2. Your Solution Review & Feedback
What You Implemented
You wrote two recursive implementations of binary search, both excellent! Let me analyze each:
First Implementation (Nested Function with Offset):
from typing import List
class Solution:
def search(self, nums: List[int], target: int) -> int:
def binary_search(nums, target, offset=0):
if len(nums) < 1:
return -1
mid_index = len(nums) // 2
if target == nums[mid_index]:
return mid_index + offset
elif target < nums[mid_index]:
left_bisection = nums[:mid_index]
return binary_search(left_bisection, target, offset)
elif target > nums[mid_index]:
right_bisection = nums[mid_index + 1:]
new_offset = offset + mid_index + 1
return binary_search(right_bisection, target, new_offset)
return binary_search(nums=nums, target=target)
Second Implementation (Method-Based with Offset):
class Solution:
def search(self, nums, target, offset=0):
l = len(nums)
if l == 0:
return -1
middle_index = l // 2
middle_value = nums[middle_index]
if middle_value == target:
return middle_index + offset
elif middle_value > target:
new_array = nums[:middle_index]
return self.search(new_array, target, offset)
elif middle_value < target:
new_array = nums[middle_index + 1:]
offset += middle_index + 1
return self.search(new_array, target, offset)
What Worked Well
Both implementations are excellent and show mastery:
- ✅ Perfect offset tracking
- ✅ Correct base cases
- ✅ Proper slicing for right half
- ✅ Good debugging
Issues Identified
Both implementations share the same optimization opportunity:
- Array slicing creates copies (O(n) space and time)
Recommended Improvements
Optimization 1: Iterative Approach (No Array Copying)
def search(self, nums: List[int], target: int) -> int:
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Optimization 2: Recursive with Index Pointers
def search(self, nums: List[int], target: int) -> int:
def binary_search_helper(left, right):
if left > right:
return -1
mid = (left + right) // 2
if nums[mid] == target:
return mid
elif nums[mid] < target:
return binary_search_helper(mid + 1, right)
else:
return binary_search_helper(left, mid - 1)
return binary_search_helper(0, len(nums) - 1)
3. Solutions Progression
Approach 1: Linear Search (Naive)
from typing import List
class Solution:
def search(self, nums: List[int], target: int) -> int:
for index in range(len(nums)):
if nums[index] == target:
return index
return -1
Time Complexity: O(n)
Space Complexity: O(1)
Approach 2: Recursive Binary Search with Array Slicing
Approach 3: Recursive Binary Search with Index Pointers
Approach 4: Iterative Binary Search
4. Pattern Recognition
Core Algorithmic Pattern: Binary Search / Divide and Conquer
Key Insight That Unlocks the Problem
The "Aha!" Moment:
Naive thinking: "I'll check each element one by one."
Breakthrough insight: "The array is sorted! If the middle element is too small, I can ignore everything to the left. If it's too large, I can ignore everything to the right."
5. Bridge to Real Systems
Real-World Applications:
- Database indexing (B-trees use binary search)
- Git bisect (finding which commit introduced a bug)
6. Common Mistakes
Mistake 1: Wrong Loop Condition
Fix: Use while left <= right:
Mistake 2: Off-by-One in Range Update
Fix: Use left = mid + 1 and right = mid - 1
Mistake 3: Wrong Initial Right Boundary
Fix: Use right = len(nums) - 1
7. Key Takeaways
1. Binary Search is About Halving
2. Your Recursive Solutions Are Correct
3. The Iterative Pattern is Standard
4. Real Systems Use This Everywhere
8. Complexity Summary Table
| Approach | Time | Space | Notes |
|---|---|---|---|
| Linear Search | O(n) | O(1) | Doesn't use sorted property |
| Recursive (slicing) | O(log n) comparisons, O(n) slicing | O(n) | Correct and elegant! |
| Recursive (pointers) | O(log n) | O(log n) | No slicing overhead |
| Iterative | O(log n) | O(1) | Production standard |