Binary Search Tutorial
Binary Search Tutorial
Abstract
This is an attempt to explain Binary Search. We’ll first try to solve
one of the fundamental problems in computer science and will try to
abstract or generalize Binary Search by solving various problems.
Contents
1 Introduction 1
1.1 Linear Search . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Better Solution aka Binary Search . . . . . . . . . . . . 1
1.3 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
3 Introductory Problems 4
1 Introduction
One of the fundamental problems in Computer Science is to retrieve the locate
an element i.e. finding it’s position in a given ordered list of numbers. More
formally the problem can be stated as follows :-
Given an array let’s say X in which the elements are sorted in ascending order.
We have to return the index of an element E if it exists in the array or return
-1.
1. X[i] > E
We know that if E exists in our array, it can only exists on the left of our
array. This is because ∀j > i, X[j] > E as well, as our array is sorted in
ascending order.
2. X[i] < E
By doing the argument similar to in the last point, we can say that if our
E exists, it can only exists on the right of i.
3. if X[i] = E
Trivial.
1
the middle of the array X, then it will lead to the overall best complexity of
O(log2 n). Proof is left as an exercise for the reader. (Hint: Try to write the
recursive definition of the complexity function.)
1.3 Code
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
int main(){
vector<int> a = {1, 2, 3, 4, 5, 6, 7, 8};
int E, l = 0, r = (int)a.size() - 1;
cin >> E;
2
2.1 The ingredients
There are essentialy two ingredients in a binary search.
• A condition on a discrete variable
• A series of discrete variables divided in two halves by the condition
Here the condition can be anything.
The only thing that must satisfy for application of binary search is that the
series of variables over which we search should be divided in two proper halves
by the condition. One half which satisfies the condition and the other which
doesn’t.
[Note: You might argue that we can apply a binary search on a continuous
variable as well. This however involves working with a precision as a terminat-
ing conditon. This is the same as dividing the continuous range into discrete
values with each subsequent value being greater than the last by the given pre-
cision. Although it can sometimes be more practical to consider our variable
continuous, but in the end it is not much different from the discrete case.]
There are two possible cases-
• If x satisfies the condition then x + 1 also satisfies.
i.e The right section of the range satisfies the condition.
• If x satisfies the condition then x − 1 also satisfies.
i.e The left section of the range satisfies the condition.
In both cases the range is divided into two halves. Now if we consider the two
sections as right and left instead of satisfying and not-satisfying, then both
cases become essentially same problems.
Now that we have established our requirements and conventions we will talk
about the two sections as left and right sections.
[Note that a section could be empty as well.]
3
2.3 The Method
The basic principle of binary search is that by checking the condition for a
value of the discrete variable, we can reduce our search space.
Let us consider the case when we want the last element of the left section. On
checking the condition
• if the value belongs to the left section, we can remove all the values which
lie further to the left.
• if the value belongs to the right section, we can remove the current value
including all the values to the right
Similarly when we want to find the first element of the right section. On
checking the condition
• if the value belongs to the left section, we can remove the current value
and all the values which lie further to the left.
• if the value belongs to the right section, we can remove all the values which
lie further to the right
It is easy to counter this however. We can simply always take the Ceil of
middle in the first case and the f loor of middle in the second.
3 Introductory Problems
Problem 1: Given a sorted array X, find the first element in X which is not
less than a given number Y . Return -1 if no such element exists.
4
Solution: First we need to check if we can apply Binary Search on it. If we
compare Y with X[mid], we can see that if Y > X[mid], then our answer will
lie to right side of mid. But if Y ≤ X[mid], then our answer will lie on the left
side of mid, with one of the possible answers being X[mid] also, so we’ll keep
it in our interval.
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
int main(){
vector<int> a = {1, 2, 3, 3, 3, 4, 5, 7, 8};
int E, l = 0, r = (int)a.size() - 1;
cin >> E;
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
5
using namespace std;
int main(){
vector<int> a = {1, 2, 3, 3, 3, 4, 5, 7, 8};
int E, l = 0, r = (int)a.size() - 1;
cin >> E;
Problem 3: Given a sorted array X, find the last index of element which is
not greater than Y or return -1 if it doesn’t exits
Solution: Let’s say we have a mid. Now if X[mid] > Y then we move to
the left side of the binary search. But if X[mid] ≤ E, then we move to the
right side of the binary search, with one of the possible solutions being mid
also. So here we are essentially taking the first element of the right side. So,
while writing the code for binary search, we’ll take our mid to be a+b+1
2 .
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
int main(){
vector<int> a = {1, 2, 3, 3, 3, 4, 5, 7, 8};
int E, l = 0, r = (int)a.size() - 1;
cin >> E;
6
int mid = l + r + 1 >> 1;
if(a[mid] > E) r = mid - 1;
else l = mid;
}
7
bool check(const vi & x, const int & mid, const int & c){
int tot = 0, i = 0, j = 0, n = (int)x.size();
while(i < n){
tot++;
while(i < n && x[i] - x[j] < mid)i++;
j = i;
}
return tot >= c;
}
int main(){
SYNC;
int T; cin >> T;
while(T--){
int n, c;
cin >> n >> c;
vi x(n);
[Note: Here we have taken the right mid because we have to combine it with
the right interval in the case when d is valid.]
8
Solution: The first thing to see intuitively is that for a given maximum
capacity let’s say C, we can fill them with the containers satisfying the con-
straints, then obviously ∀C 0 ≥ C we can fill the containers satisfying the given
constraints. But if for a maximum capacity we can’t fill the containers with
the vessels satisfying the given constraints [Note: This will happen if the
total containers required to be filled by vessels exceed the total con-
tainers given in the problem or there is a vessel with capacity Vi > C]
then ∀C 0 ≤ C we can’t fill the containers as that would require even more
containers or we can get a vessel with capacity > C 0 . Hence our condition for
binary search satisfies.
Now How to check if a particular maximum capacity C is valid? We start
from the first vessel and keep filling the first container until the sum of the
capacities of all the vessels ≤ C. After that we start again greedily to fill
another containers. And then we’ll check in the end if containers filled are
≤ m.You can prove that it will be the most optimum way to ensure least
numbers of containers filled. [Proof is left as an exercise for the reader.].
#include <bits/stdc++.h>
using namespace std;
int n, m, V[1000];
int check(int c) {
int cap = 0, cnt = 0;
int i = 0;
for(i = 0; i < n; i++) {
if(V[i] > c)
return 1000000;
if(cap < V[i])
cap = c, cnt++;
cap -= V[i];
}
return cnt;
}
int main() {
int i;
9
while(scanf("%d %d", &n, &m) == 2) {
for(i = 0; i < n; i++)
scanf("%d", &V[i]);
int l = 1, r = 1000000*n;
while(l < r) {
int mid = (l + r)>> 1;
int cnt = check(mid);
if(cnt > m)
l = mid+1;
else
r = mid;
}
printf("%d\n", l);
}
return 0;
}
LL ch(LL mid){
LL ans = 0;
for(LL k = 2; k <= mid && k * k <= mid && k * k * k <=
,→ mid; k++){
ans += mid/(k * k * k);
}
10
return ans;
}
int main(){
SYNC;
LL m;
cin >> m;
LL l = 0, r = (LL)1e16;
vi v;
int ch(int mid, int n){
int ans = 0, ptr = 0;
11
while(ptr < n){
int i = ptr; ptr++;
while(ptr < n && v[i] + mid >= v[ptr]){
ptr++;
}
ans++;ptr--;i = ptr;
while(ptr < n && v[ptr] - v[i] <= mid)
ptr++;
}
return ans;
}
int main(){
SYNC;
int n , k; cin >> n >> k;
v.resize(n);
REP(i, n){
cin >> v[i];
}
sort(ALL(v));
int l = 0 , r = v[n - 1] + 1;
REP(i , 32){
int mid = l + r >> 1;
if(ch(mid, n) > k){
l = mid + 1;
}
else
r = mid;
}
12
cout << r << endl;
return 0;
}
int main(){
SYNC;
int T; int caseno = 1;
cin >> T;
13
while(T--){
cout << "Case #" << caseno++ << ": ";
int b, n;
cin >> b >> n;
vi m(b);
REP(i, b) cin >> m[i];
LL l = 0, r = (LL)1e15;
while(l < r){
auto mid = l + r >> 1;
if(ch(mid, m, n))
r = mid;
else l = mid + 1;
}
int ans = 0;
REP(i, b){
n -= (l - 1)/m[i] + 1;
}
REP(i, b){
if(l % m[i])continue;
n--;
if(!n){
ans = i + 1; break;
}
}
cout << ans << endl;
}
return 0;
}
14