binary-search-template
Binary search is a powerful tool for efficiently solving problems that involve a monotonic function. To use binary search effectively, you must decide how to adjust the search range based on the problem's requirements — whether you're looking for the first feasible candidate or the last feasible candidate. This post provides a detailed template and discusses how to adapt it for different scenarios.
The Core Idea
Binary search works on problems where the data or condition exhibits a monotonic behavior. That is, as you move along the search range:
- A condition either transitions from
False
toTrue
(non-decreasing) or fromTrue
toFalse
(non-increasing).
In binary search, the goal is to narrow the search range until the target element or condition is located. To achieve this, the update logic depends on whether we are moving left or right for the next feasible candidate.
Binary Search Template
The template is structured to handle both cases — finding the first feasible candidate and the last feasible candidate.
def binary_search(arr, target):
left, right = 0, len(arr) - 1
result = -1 # Default if no solution is found
while left <= right:
mid = (left + right) // 2
if feasible(mid): # Feasible condition depends on the problem
result = mid # Update the result
# Adjust search range depending on the problem
right = mid - 1 # Move left for the first feasible candidate
# left = mid + 1 # Uncomment to move right for the last feasible candidate
# As a general rule, we need to figure out in which half our next feasible candidate would be found
# and accordingly shrink the range.
else:
# Adjust search range to exclude the non-feasible mid
left = mid + 1 # Move right if condition is not met
# right = mid - 1 # Uncomment to move left if needed
return result
Adapting the Template
-
Finding the First Feasible Candidate:
- If the feasible condition is satisfied at
mid
, the result is updated tomid
. - Narrow the search to the left half (
right = mid - 1
) to find earlier occurrences.
- If the feasible condition is satisfied at
-
Finding the Last Feasible Candidate:
- If the feasible condition is satisfied at
mid
, the result is updated tomid
. - Narrow the search to the right half (
left = mid + 1
) to find later occurrences.
- If the feasible condition is satisfied at
Example 1: Find the First True
Given a boolean array [False, False, True, True, True]
, find the index of the first True
.
Feasible Function:
def feasible(mid):
return arr[mid] == True
Solution:
def find_first_true(arr):
left, right = 0, len(arr) - 1
result = -1
while left <= right:
mid = (left + right) // 2
if arr[mid]: # Feasible condition: arr[mid] == True
result = mid
right = mid - 1 # Move left for the first feasible candidate
else:
left = mid + 1 # Move right if not feasible
return result
Output:
arr = [False, False, True, True, True]
print(find_first_true(arr)) # Output: 2
Example 2: Find the Last False
Given the same boolean array, find the index of the last False
.
Feasible Function:
def feasible(mid):
return arr[mid] == False
Solution:
def find_last_false(arr):
left, right = 0, len(arr) - 1
result = -1
while left <= right:
mid = (left + right) // 2
if not arr[mid]: # Feasible condition: arr[mid] == False
result = mid
left = mid + 1 # Move right for the last feasible candidate
else:
right = mid - 1 # Move left if not feasible
return result
Output:
arr = [False, False, True, True, True]
print(find_last_false(arr)) # Output: 1
Key Insights
-
Adjusting the Search Range:
- The direction you adjust the range (
left
orright
) depends on whether you're looking for the first or * last* occurrence. - Always test your range adjustments to ensure correctness.
- The direction you adjust the range (
-
Feasible Function:
- The feasible function encapsulates the problem's constraints and determines whether the current index satisfies the condition.
-
Boundary Conditions:
- Handle edge cases, such as when the array contains no feasible candidates (e.g., all
False
or allTrue
).
- Handle edge cases, such as when the array contains no feasible candidates (e.g., all
Real-World Applications
Binary search with monotonic functions is widely applicable:
- Find the smallest/largest element that satisfies a condition (e.g., minimum capacity to transport goods within a deadline).
- Allocate resources (e.g., minimum number of servers required to handle traffic).
- Search for a boundary in sorted data (e.g., pivot points in rotated arrays).
Conclusion
Binary search is more than just a tool for sorted arrays. Its power lies in efficiently narrowing down a search range based on a monotonic feasibility function. By understanding how to adapt the search range for different problems, you can apply binary search to a wide variety of scenarios.
Let the problem guide you: Are you looking for the first feasible candidate or the last feasible candidate? Once you answer this, the binary search template will do the rest!