Contents

# Background

As I am preparing for interviews recently, this is a good time to write about my experience in writing a binary search. Please correct me if I get anything wrong in the content.

# Binary Search

Binary search is always one of the most popular algorithms and at the same time tough to implement correctly. It is surprisingly hard to find a correct while easy-to-modify binary search code out there.

In the book Programming Pearls, the author mentioned that only 10% of the programmers can write a bug-free binary search.

Here I will discuss my experience in writing it and highlight some common mistakes in implementing the algorithm.

# Example Problem

Let’s say we want to find the first target in an array.

e.g. Finding the index of the first `5` in the array `[1, 2, 3, 4, 5, 5, 6]`. The answer is `4` (0-based).

# Approach

## Invariant

The key to write a correct binary search is to maintain (and keep in mind of) a loop invariant which will be true at the beginning and the end of each loop as suggested in Programming Pearls.

For example, let’s call the array `A`, target `target`, lower bound `lo` and upper bound `hi`. We want an invariant `A[lo] <= target <= A[hi]` and `lo < hi`. A natural pick of `lo` and `hi` will be `0` and `N-1` where `N` is the size of `A`.

The loop condition is `lo < hi` because it looks most natural. It means when the size of search space is larger than 1, continue to search.

## Transition

The existence of the invariant facilitates the development of the rest of the algorithm. The next important decision is the transition in each round of the loop.

The variable `mid` is at the center of `lo` and `hi`, intuitively, `(lo + hi) / 2` (this is buggy, but we’ll fix this later).

There are 2 cases. - if `A[mid] >= target` - `target` must be in the range `A[0..mid]`. In order to maintain the invariant, the transition will be `hi = mid`. - else, i.e. `A[mid] < target` - As the target is not in `A[0..mid]`, we’ll discard this range, making the transition `lo = mid + 1`.

## Loop Termination & Verification

When `lo < hi` does not hold, the loop terminates.

Will it always terminate? Yes, because either `lo` increases or `hi` decreases in each round. There is still a trick though, please see section Dead Loop.

The loop terminates even if there does not exist a solution. We’ll have to verify it. It is a simple if-then-else statement. The final search index is stored in `lo`.

• if `A[lo] == target`
• exists a solution
• else
• no solution

# Traps

## Integer Overflow

### Problem

This can be hard to observe but when the result of `lo + hi` exceeds the limit of integer, things will go wrong.

### Solution

The solution is to change

``````mid = (lo + hi) / 2
``````

to

``````mid = lo + (hi - lo) / 2
``````

### Problem

Let’s say now we want to find the last index of the target in the array `A`.

e.g. Finding the index of the first `5` in the array `[1, 2, 3, 4, 5, 5, 6]`. The answer is `5` (0-based).

Following the steps above, we’re tempted to change the algorithm as follows:

• if `A[mid] <= target`
• `lo = mid`
• else, i.e. `A[mid] > target`
• `hi = mid - 1`

However, this creates dead loop because of the integer division in the calculation of `mid`.

For example, in the case of `lo = 0`, `hi = 1` and `A[mid] <= A[lo]`, the algorithm will loop between `mid = lo + (hi - lo) / 2`, `A[mid] <= target` and `lo = mid` until the end of the world.

The core of the problem is that the statement “either `lo` increases or `hi` decreases in each round” is not true any more. `lo` may stay unchanged under certain conditions.

### Attempt

The solution is to change `mid = lo + (hi - lo) / 2` to `mid = lo + (hi - lo + 1) / 2` (beware: integer overflow again!) such that the division will round up instead of round down.

### Final Solution

This quick solution suffers from integer overflow again because when `hi = INT_MAX` and `lo = 0`, `hi - lo + 1` will overflow. The fix would look like `mid = lo + (hi - lo) / 2 + (hi - lo) % 2`.

# Final Values

## Problem

We have `lo`, `mid` and `hi` in our algorithm. Which one should we use as the answer?

## Solution

In our case of our loop condition `lo < hi`, we can safely conclude that when the statement evaluates to `false`, `lo` is equal to `hi`. Of course, this also depends on how the transition is written.

Anyway it is still much easier to understand than something like `lo <= hi` and `lo < hi - 1` because `lo` and `hi` will be different when the algorithm terminates.

# Variants

There are many variants of binary search. Finding first target, finding last target, finding the first value greater than target, finding the first value smaller than target, etc. These are left as an exercise.

Hopefully with the above tips in mind, it will be easier to write a bug-free and readable binary search.

# Full Code

Here is the full code of the original “find first target” problem in C++:

``````int binary_search(vector<int> A, int target){
int N = A.size();
int lo = 0, hi = N - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (A[mid] >= target) hi = mid;
else lo = mid + 1;
}
return A[mid]==target?lo:-1;
}
``````
Tags// ,