PrevNext
Somewhat Frequent
 0/19

Binary Search

Authors: Darren Yao, Abutalib Namazov, Andrew Wang, Qi Wang

Binary searching on arbitrary monotonic functions and built-in functions for binary search.

Introduction

Resources
CSAanimation, code, lower_bound + upper_bound
CPHcode, lower_bound + upper_bound, some applications
CFvideos, problems similar to those covered in this module
KAplenty of diagrams, javascript implementation
IUSACOmodule is based off this
TCsimilar material
LCmany problems & applications

When we binary search on an answer, we start with a search space of size NN which we know the answer lies in. Then each iteration of the binary search cuts the search space in half, so the algorithm tests O(logN)\mathcal{O}(\log N) values. This is efficient and much better than testing each possible value in the search space.

Binary Searching on Monotonic Functions

Let's say we have a boolean function f(x). Usually, in such problems, we want to find the maximum or minimum value of xx such that f(x) is true. Similarly to how binary search on an array only works on a sorted array, binary search on the answer only works if the answer function is monotonic, meaning that it is always non-decreasing or always non-increasing.

Finding The Maximum x Such That f(x) = true

We want to construct a function lastTrue such that lastTrue(lo, hi, f) returns the last x in the range [lo,hi] such that f(x) = true. If no such x exists, then lastTrue should return lo-1.

This can be done with binary search if f(x) satisfies both of the following conditions:

  • If f(x) = true, then f(y) = true for all yxy \leq x.
  • If f(x) = false, then f(y) = false for all yxy \geq x.

For example, if f(x) is given by the following function:

f(1) = true
f(2) = true
f(3) = true
f(4) = true
f(5) = true
f(6) = false
f(7) = false
f(8) = false

then lastTrue(1, 8, f) = 5 and lastTrue(7, 8, f) = 6.

Implementation 1

Verify that this implementation doesn't call f on any values outside of the range [lo,hi].

C++

#include <bits/stdc++.h>
using namespace std;
int lastTrue(int lo, int hi, function<bool(int)> f) {
for (--lo; lo < hi; ) {
int mid = lo+(hi-lo+1)/2;
// find the middle of the current range (rounding up)
if (f(mid)) lo = mid;
// if mid works, then all numbers smaller than mid also work
else hi = mid-1;

See Lambda Expressions if you're not familiar with the syntax used in the main function.

Java

import java.util.function.Predicate;
public class BinarySearch {
public static void main(String[] args) {
System.out.println(lastTrue(2,10,(x) -> true)); // 10
// all numbers satisfy the condition
System.out.println(lastTrue(2,10,(x) -> x*x<=30)); // 5
System.out.println(lastTrue(2,10,(x) -> false)); // 1
// no numbers satisfy the condition
}

See Java Predicate Interface if you're not familiar with the Predicate interface used.

Python

def lastTrue(lo, hi, f):
""" Binary Search
:param f: (lambda function) check a state
:param lo: (int) lower bound
:param hi: (int) upper bound
:return res: (int) the maximum x such that f(x) is true
"""
res = lo-1
while lo <= hi:
mid = (lo+hi)//2 # find the middle of the current range

See Lambda Expressions if you're not familiar with the syntax used in the program.

Implementation 2

This approach is based on interval jumping. Essentially, we start from the beginning of the array, make jumps, and reduce the jump length as we get closer to the target element. We use powers of 2, very similiar to Binary Jumping.

C++

int lastTrue(int lo, int hi, function<bool(int)> f) {
for (int dif = (hi-(--lo)); dif; dif /= 2)
while (lo+dif <= hi && f(lo+dif)) lo += dif;
return lo;
}

Java

public static int lastTrue(int lo, int hi, Predicate<Integer> f) {
for (int dif = (hi-(--lo)); dif > 0; dif /= 2)
while (lo+dif <= hi && f.test(lo+dif)) lo += dif; //f is the function
return lo;
}

Python

def lastTrue(lo, hi, f):
lo-=1
while lo < hi:
mid = (lo+hi+1)//2
if f(mid):
lo = mid
else:
hi = mid-1
return lo

Finding The Minimum x Such That f(x) = true

We want to construct a function firstTrue such that firstTrue(lo, hi, f) returns the first x in the range [lo,hi] such that f(x) = true. If no such x exists, then firstTrue should return hi+1.

Similarly to the previous part, this can be done with binary search if f(x) satisfies both of the following conditions:

  • If f(x) is true, then f(y) is true for all yxy \geq x.
  • If f(x) is false, then f(y) is false for all yxy \leq x.

We will need to do the same thing, but when the condition is satisfied, we will cut the right part, and when it's not, the left part will be cut.

C++

#include <bits/stdc++.h>
using namespace std;
int firstTrue(int lo, int hi, function<bool(int)> f) {
for (hi ++; lo < hi; ) {
int mid = lo+(hi-lo)/2;
if (f(mid)) hi = mid;
else lo = mid+1;
}
return lo;

Java

import java.util.function.Predicate;
public class BinarySearch {
public static void main(String[] args) {
System.out.println(firstTrue(2,10,(x) -> true)); // 2
System.out.println(firstTrue(2,10,(x) -> x*x>=30)); // 6
System.out.println(firstTrue(2,10,(x) -> false)); // 11
}
public static int firstTrue(int lo, int hi, Predicate<Integer> f) {

Python

def firstTrue(lo, hi, f):
# returns smallest x in [lo,hi] that satisfies f
# hi+1 if no x satisfies f
hi+=1
while lo < hi:
mid = (lo+hi)//2
if f(mid):
hi = mid
else:
lo = mid + 1
return lo
print(firstTrue(2, 10, lambda x : True)) # 2
print(firstTrue(2, 10, lambda x : x * x >= 30)) # 6
print(firstTrue(2, 10, lambda x : False)) # 11

Example - Maximum Median

Focus Problem – read through this problem before continuing!

Statement: Given an array arr\texttt{arr} of nn integers, where nn is odd, we can perform the following operation on it kk times: take any element of the array and increase it by 11. We want to make the median of the array as large as possible after kk operations.

Constraints: 1n2105,1k1091 \leq n \leq 2 \cdot 10^5, 1 \leq k \leq 10^9 and nn is odd.

Solution

Common Mistakes

Mistake 1 - Off By One

Consider the code from CSAcademy's Binary Search on Functions.

C++

long long f(int x) {
return (long long)x * x;
}
int square_root(int x) {
int left = 0, right = x;
while (left < right) {
int middle = (left + right) / 2;
if (f(middle) <= x) {
left = middle;
} else {

Java

public static long f(int x) {
return (long)x * x;
}
public static int square_root(int x) {
int left = 0, right = x;
while (left < right) {
int middle = (left + right) / 2;
if (f(middle) <= x) {
left = middle;
} else {

Python

def f(x):
return x*x
def square_root(x):
left = 0
right = 0
while left < right:
middle = (left + right) // 2
if f(middle) <= x:
left = middle
else:
right = middle - 1
return left

This results in an infinite loop if left=0 and right=1! To fix this, set middle = (left+right+1)/2 instead.

Mistake 2 - Not Accounting for Negative Bounds

Consider a slightly modified version of firstTrue:

C++

int firstTrue(int lo, int hi, function<bool(int)> f) {
for (hi ++; lo < hi; ) {
int mid = (lo+hi)/2;
if (f(mid)) hi = mid;
else lo = mid+1;
}
return lo;
}

Java

public static int firstTrue(int lo, int hi, Predicate<Integer> f) {
for (hi ++; lo < hi; ) {
int mid = (lo+hi)/2;
if (f.test(mid)) hi = mid;
else lo = mid+1;
}
return lo;
}

Python

def firstTrue(lo, hi, f):
hi+=1
while lo < hi:
mid = (lo+hi)//2
if f(mid):
hi = mid
else:
lo = mid+1
return lo

This code does not necessarily work if lo is negative! Consider the following example:

C++

int main() {
cout << firstTrue(-10,-10,[](int x) { return false; }) << "\n";
// -8, should be -9
cout << firstTrue(-10,-10,[](int x) { return true; }) << "\n";
// infinite loop
}

Java

public static void main(String[] args) {
System.out.println(firstTrue(-10,-10,(x) -> false));
// -8, should be -9
System.out.println(firstTrue(-10,-10,(x) -> true));
// infinite loop
}

Python

print(firstTrue(-10,-10,lambda x : False))
# -8, should be -9
print(firstTrue(-10,-10,lambda x : True))
# infinite loop

This is because dividing an odd negative integer by two will round it up instead of down.

C++

int firstTrue(int lo, int hi, function<bool(int)> f) {
for (hi ++; lo < hi; ) {
int mid = lo+(hi-lo)/2;
if f(mid) hi = mid;
else lo = mid+1;
}
return lo;
}

Java

public static int firstTrue(int lo, int hi, Predicate<Integer> f) {
for (hi ++; lo < hi; ) {
int mid = lo+(hi-lo)/2;
if(f.test(mid)) hi = mid;
else lo = mid+1;
}
return lo;
}

Python

def firstTrue(lo, hi, f):
hi+=1
while lo < hi:
mid = lo+(hi-lo)/2
if f(mid):
hi = mid
else:
lo = mid+1
return lo

Mistake 3 - Integer Overflow

The first version of firstTrue won't work if hi-lo initially exceeds INT_MAX, while the second version of firstTrue won't work if lo+hi exceeds INT_MAX at any point during execution. If this is an issue, use long longs instead of ints.

Library Functions For Binary Search

C++

Resources
CPPwith examples

Example - Counting Haybales

Focus Problem – read through this problem before continuing!

As each of the points are in the range 010000000000 \ldots 1\,000\,000\,000, storing locations of haybales in a boolean array and then taking prefix sums of that would take too much time and memory.

Instead, let's place all of the locations of the haybales into a list and sort it. Now we can use binary search to count the number of cows in any range [A,B][A,B] in O(logN)\mathcal{O}(\log N) time.

With Built-in Function

C++

We can use the the built-in lower_bound and upper_bound functions.

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using vi = vector<int>;
#define pb push_back
#define rsz resize
#define all(x) begin(x), end(x)
#define sz(x) (int)(x).size()

Java

We can use the builtin Arrays.binarySearch function.

import java.io.*;
import java.util.*;
public class haybales{
public static void main(String[] args) throws IOException
{
BufferedReader br = new BufferedReader(new FileReader(new File("haybales.in")));
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("haybales.out")));
StringTokenizer st = new StringTokenizer(br.readLine());

Python

We can use the builtin bisect.bisect function.

from bisect import bisect
inp = open("haybales.in", 'r')
out = open("haybales.out", 'w')
N, Q = map(int, inp.readline().split())
arr = sorted(list(map(int, inp.readline().split())))
for i in range(Q):

Without Built-in Function

C++

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using vi = vector<int>;
#define pb push_back
#define rsz resize
#define all(x) begin(x), end(x)
#define sz(x) (int)(x).size()

Java

import java.util.*;
import java.io.*;
public class Haybales{
static int N, Q;
public static void main(String[] args) throws IOException{
Kattio io = new Kattio("haybales");
N = io.nextInt();
Q = io.nextInt();
List<Integer> v = new ArrayList<>();

Problems

USACO

StatusSourceProblem NameDifficultyTagsSolutionURL
SilverEasy
Show Tags

Binary Search, Ordered Set

SilverEasy
Show Tags

Binary Search, Sorting

SilverEasy
Show Tags

Binary Search, Sorting

SilverNormal
Show Tags

Binary Search, Sorting

GoldHard
Show Tags

Binary Search, Sorting

External Sol
SilverVery Hard
Show Tags

Binary Search, Sqrt

External Sol
PlatInsane
Show Tags

Binary Search, Sorting

General

StatusSourceProblem NameDifficultyTagsSolutionURL
CFEasy
Show Tags

Binary Search

CSESEasy
Show Tags

Binary Search

CSESEasy
Show Tags

Binary Search

CSESNormal
Show Tags

Binary Search

CFNormal
Show Tags

Binary Search, Prefix Sums

CFNormal
Show Tags

Binary Search

Check CF
CFNormal
Show Tags

Binary Search

CFHard
Show Tags

Binary Search

CFHard
Show Tags

Binary Search

Check CF
Baltic OIVery Hard
Show Tags

Binary Search

Module Progress:

Join the USACO Forum!

Stuck on a problem, or don't understand a module? Join the USACO Forum and get help from other competitive programmers!

PrevNext