Binary Search🔗
Binary Search is a powerful algorithm to find an element or solve optimization problems in a search space where the data satisfies specific properties. Below is an easy-to-follow summary and framework for applying Binary Search effectively:
Key Concepts
-
Search Space:
- A set of elements (e.g., indices, numbers, or a conceptual range) that can be ordered or partitioned. Examples: Sorted arrays, monotonic functions, or intervals.
-
Predicate (p(x)):
- A boolean function (true/false) applied to items in the search space.
- The predicate determines the behavior of the elements:
F*T*
: false, false, false, true, true (transition from F to T).T*F*
: true, true, true, false, false (transition from T to F).
- Applicability of Binary Search:
- Binary Search is applicable if:
- The search space is monotonic w.r.t. the predicate.
- For
F*T*
: All F precede T. - For
T*F*
: All T precede F.
- Binary Search is applicable if:
- Goals of Binary Search:
- Find the last F or the first T in
F*T*
. - Find the last T or the first F in
T*F*
.
- Find the last F or the first T in
Framework to Solve Binary Search Problem🔗
-
Define the Search Space:
- Decide whether it is a range ([lo, hi]) or a collection (like an array).
-
Define the Predicate (p(x)):
- Write a condition that transitions at a key point.
Example: For a sorted array and a target x, use p(x) = (arr[mid] >= target) for `F*T*
.
-
Decide the Goal:
- Find first T, last F, last T, or first F based on the problem.
-
Write Binary Search:
- Use the appropriate loop (while(lo < hi) or while(lo <= hi)) and mid-point calculation:
- low_mid: mid = lo + (hi - lo) / 2 (default).
- high_mid: mid = lo + (hi - lo + 1) / 2 (if focusing on higher mid).
- Use the appropriate loop (while(lo < hi) or while(lo <= hi)) and mid-point calculation:
Pseudo Codes🔗
For F*T*
(Find Last F/First T)🔗
int lastF(int lo, int hi) {
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2; // Use high_mid
if (p(mid))
hi = mid - 1; // Move left
else
lo = mid; // Move right
}
return (!p(lo)) ? lo : -1; // Check and return
}
int firstT(int lo, int hi) {
while (lo < hi) {
int mid = lo + (hi - lo) / 2; // Use low_mid
if (p(mid))
hi = mid; // Move left
else
lo = mid + 1; // Move right
}
return (p(lo)) ? lo : -1; // Check and return
}
For T*F*
(Find Last T/First F)🔗
int lastT(int lo, int hi) {
while (lo < hi) {
int mid = lo + (hi - lo + 1) / 2; // Use high_mid
if (p(mid))
lo = mid; // Move right
else
hi = mid - 1; // Move left
}
return (p(lo)) ? lo : -1; // Check and return
}
int firstF(int lo, int hi) {
while (lo < hi) {
int mid = lo + (hi - lo) / 2; // Use low_mid
if (p(mid))
lo = mid + 1; // Move right
else
hi = mid; // Move left
}
return (!p(lo)) ? lo : -1; // Check and return
}
Tips to Remember🔗
-
Predicate: Design it to divide the search space into
F*T*
orT*F*
. -
Mid Calculation:
- low_mid: lo + (hi - lo) / 2 (default for most cases).
- high_mid: lo + (hi - lo + 1) / 2 (if skipping mid element is required).
-
Focus: Adjust lo or hi based on whether you move left or right.
-
Post-Loop Check: Always verify the result before returning to avoid off-by-one errors.
Binary Search simplifies problems when you clearly define the search space and the predicate.