---
# System prepended metadata

title: Binary Search in General
tags: [Binary Search, Algorithm]

---

# Binary Search in General

## Intuition

Suppose we are given a **sorted** array, say 
$$A = [1, 1, 2, 4, 6, 9]$$
then we can see that all numbers in $A[0:3]$ are **smaller** than $4$, while all in $A[3:6]$ are **equal or greater than** $4$.

Thus, we can define a **predicate**, 
$$f(x) := (x >= 4)$$

Let $B$ be the array of $f(A)$, then
$$B = [0, 0, 0, 1, 1, 1]$$

As we can see, $B$ is of the form $0^*1^*$ (here I'm abusing the usage of regex, but it's just to clarify that we will first have contigous $0$'s and then contiguous $1$'s in the predicate array.)

## Algorithm
The following algorithm will help us find the **smallest** index $i$ in $B$ for which the predicate $f(i)$ holds true.

That is, we want to find the **first** $1$ in $B$.
(Keep this in mind !)

```python=
function find(n, f):
    i, j := 0, n
    while i < j:
        mid = (i+j)/2   # floor
        if f(mid):
            j = m       # preserve f(j) = 1
        else
            i = m+1     # preserve f(i-1) = 0
    return i            # Note i == j here
```

## Proof of Correctness
Let $n = len(A)$

Define $f(-1) = 0, f(n) = 1$

### Lemma 1
Invariant:
- $f(i-1) = 0$
- $f(j) = 1$

#### Proof by induction
- At first, $f(-1) = 0$ and $f(n) = 1$, true.
- Later on, if $f(mid) = 0$, then we set $i = mid+1$, preserving $f(i-1) = 0$.
- if $f(mid) = 1$, then we set $j = mid$, still $f(j) = 1$ is preserved.

### Lemma 2
When the loop in line 3 breaks, $i = j$ 
#### Proof
Since $i<j$ at first, we have $i \le\ mid < j$ (since we always take mid using floor).

Consider the last iteration, 
- If we update i, since $mid+1 \le\ j$, then $i$ must be $j$
- If we update j, since $i \le\ mid$, then $j$ must be $i$

### Theorem
By the property of sorted array, and Lemma 1, 2.
We can confirm that the returned index $i$ is the **smallest** index such that $f(i) = 1$


## Application
### Find the first element greater or equal to x
Let $f(v) = (v \geq x)$
```go=
func firstGreaterEqual(x int, nums []int) int {
    i, j := 0, len(nums)
    for i < j {
        m := int(uint(l+r)>>1)  // prevent overflow
        
        if nums[m] >= x {  // f(v) = (v >= x)
            j = m
        } else {
            i = m+1
        }
    }
    
    return i
}
```

### Find the largest element < x
```go=
func largestLess(x int, nums []int) int {
    // can you see why ?
    return firstGreaterEqual(x, nums)-1  
}
```

### Find the last x if duplicated x exist in A
Let $f(v) = (v > x)$
```go=
func lastDuplicate(x int, nums []int) int {
    i, j := 0, len(nums)
    for i < j {
        m := int(uint(i+j)>>1)
        
        if nums[m] > x {
            j = m
        } else {
            i = m+1
        }
    }
    return i - 1
}
```

### Find the first x if duplicated x exist in A
Let $f(v) = (v \geq x)$
```go=
func firstDuplicate(x int, nums []int) int {
    i, j := 0, len(nums)
    for i < j {
        m := int(uint(i+j)>>1)
    
        if nums[m] >= x {
            j = m
        } else {
            i = m+1
        }
    }
    
    return i
}
```

###### tags: `Algorithm` `Binary Search`