Linear & Binary Search

লিনিয়ার ও বাইনারি সার্চ

Read: ~30 min Intermediate 8 practice problems Live code runner

1. The Search Problem

Searching is the most common operation in computing. Given a collection of items and a target, we want to know: does the target exist, and if so, where? From looking up a phone number in your contacts to finding a row in a database, every system relies on search.

সার্চিং হলো কম্পিউটিং-এর সবচেয়ে সাধারণ কাজ। একটি collection এবং একটি target দেওয়া থাকলে আমরা জানতে চাই — target-টি আছে কি না, এবং থাকলে কোথায়? ফোনের contact খোঁজা থেকে শুরু করে database-এ row খোঁজা — সব জায়গায় search ব্যবহৃত হয়।
Today's insight
Binary search-এর মাত্র ৫ লাইন কোড — কিন্তু off-by-one bug এড়াতে নিয়মটা শিখলে সারা জীবন কাজে লাগে।

2. Linear Search — The Honest Approach

Linear search examines every element from index 0 to n-1 until it finds the target or exhausts the array. It works on any collection — sorted or unsorted, array or linked list. Time complexity is O(n).

Linear search প্রতিটি element ০ থেকে n-১ পর্যন্ত একে একে দেখে — যতক্ষণ না target পায় বা array শেষ হয়। এটি যে কোনো collection-এ কাজ করে — sorted হোক বা না হোক। সময় জটিলতা O(n)।
linear_search.cpp
#include <bits/stdc++.h>
using namespace std;

int linearSearch(const vector<int>& a, int target) {
    for (int i = 0; i < (int)a.size(); i++) {
        if (a[i] == target) return i;
    }
    return -1;
}

int main() {
    vector<int> a = {7, 3, 9, 1, 42, 5};
    cout << "index of 42 = " << linearSearch(a, 42) << "\n";
    cout << "index of 99 = " << linearSearch(a, 99) << "\n";
    return 0;
}

3. Binary Search — Halving the World

If the array is sorted, we can do dramatically better. Compare the target to the middle element: if equal — done; if smaller — search the left half; if larger — search the right half. Each step throws away half the search space, giving O(log n) time.

যদি array sorted হয়, তবে অনেক দ্রুত কাজ করা যায়। মাঝের element-এর সাথে target মিলিয়ে দেখি — সমান হলে শেষ; ছোট হলে বাম অর্ধে; বড় হলে ডান অর্ধে খুঁজি। প্রতি ধাপে অর্ধেক ফেলে দেই — তাই O(log n) সময়।
Pass 1 2 5 8 12 16 23 38 42 56 63 71 77 82 85 90 99 Pass 2 56 63 71 77 82 85 90 99 Pass 3 63 71 77 Pass 4 63 ✓ Target 63 found in 4 comparisons (log₂16 = 4) Figure 11.1 — ১৬টি element-এ 63 খুঁজতে binary search মাত্র ৪টি comparison করে।
binary_search.cpp
#include <bits/stdc++.h>
using namespace std;

// Iterative binary search — returns index or -1.
int binarySearch(const vector<int>& a, int target) {
    int lo = 0, hi = (int)a.size() - 1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;   // avoid overflow
        if (a[mid] == target) return mid;
        if (a[mid] < target) lo = mid + 1;
        else                 hi = mid - 1;
    }
    return -1;
}

int main() {
    vector<int> a = {2,5,8,12,16,23,38,42,56,63,71,77,82,85,90,99};
    cout << "index of 63 = " << binarySearch(a, 63) << "\n";
    cout << "index of 50 = " << binarySearch(a, 50) << "\n";
    return 0;
}
Off-by-one trap Use mid = lo + (hi - lo) / 2 instead of (lo+hi)/2 — for very large lo + hi the second form overflows.

খুব বড় lo + hi-এর জন্য (lo+hi)/2 overflow করতে পারে। তাই lo + (hi-lo)/2 লেখাই নিরাপদ।

4. Lower Bound & Upper Bound

lower_bound(x) returns the first index i such that a[i] >= x. upper_bound(x) returns the first index i such that a[i] > x. These two functions are the Swiss army knife of competitive programming — count occurrences, find insertion point, range queries, all reduce to them.

lower_bound(x) এমন প্রথম index i ফেরত দেয় যেখানে a[i] >= x। upper_bound(x) দেয় প্রথম index যেখানে a[i] > x। এই দুটি ফাংশন competitive programming-এ অসংখ্য সমস্যা সমাধান করে — count, insertion point, range query সব।
bounds.cpp
#include <bits/stdc++.h>
using namespace std;

// First i with a[i] >= x
int lowerBound(const vector<int>& a, int x) {
    int lo = 0, hi = (int)a.size();   // note: hi = n, not n-1
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] < x) lo = mid + 1;
        else            hi = mid;
    }
    return lo;
}

// First i with a[i] > x
int upperBound(const vector<int>& a, int x) {
    int lo = 0, hi = (int)a.size();
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] <= x) lo = mid + 1;
        else             hi = mid;
    }
    return lo;
}

int main() {
    vector<int> a = {1,2,2,2,5,7,9};
    cout << "lower_bound(2) = " << lowerBound(a, 2) << "\n";
    cout << "upper_bound(2) = " << upperBound(a, 2) << "\n";
    cout << "count of 2     = " << (upperBound(a,2) - lowerBound(a,2)) << "\n";
    return 0;
}
FunctionReturnsবাংলায়
lower_bound(x)first i with a[i] ≥ xপ্রথম index যেখানে value x বা তার বেশি
upper_bound(x)first i with a[i] > xপ্রথম index যেখানে value x-এর চেয়ে বড়
upper_bound − lower_boundcount of xx সংখ্যা কতবার আছে
lower_bound − 1largest index with value < xx-এর চেয়ে ছোট সর্বশেষ index

5. Binary Search on the Answer

The most powerful generalisation: if a problem has a monotone predicate P(x) (false for small x, true for large x — or vice versa), we can binary-search for the smallest x where P holds. The classic example: Allocate Books.

Problem. Given n books with page counts and k students, allocate consecutive books to each student so the maximum pages assigned to any student is minimised.

সমস্যা. n-টি বইয়ের page সংখ্যা এবং k জন ছাত্র দেওয়া আছে। প্রতি ছাত্রকে ধারাবাহিক বই বরাদ্দ করতে হবে এমনভাবে যাতে কোনো একজন ছাত্রের সর্বাধিক page-সংখ্যা সর্বনিম্ন হয়। এটিই Bangladesh-এর textbook board-এ বই বিতরণের মতো একটি সমস্যা।
Why monotone? If a maximum pages of M is feasible for k students, then any larger M' > M is also feasible. So the predicate feasible(M) is monotone — perfect for binary search.
allocate_books.cpp
#include <bits/stdc++.h>
using namespace std;

// Can we allocate so that no student gets > cap pages?
bool feasible(const vector<int>& pages, int k, int cap) {
    int students = 1, sum = 0;
    for (int p : pages) {
        if (p > cap) return false;
        if (sum + p > cap) { students++; sum = p; }
        else                  sum += p;
    }
    return students <= k;
}

int allocateBooks(vector<int>& pages, int k) {
    int lo = *max_element(pages.begin(), pages.end());
    int hi = accumulate(pages.begin(), pages.end(), 0);
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (feasible(pages, k, mid)) hi = mid;
        else                          lo = mid + 1;
    }
    return lo;
}

int main() {
    vector<int> pages = {12, 34, 67, 90};
    cout << "min max pages with 2 students = "
         << allocateBooks(pages, 2) << "\n";
    return 0;
}

6. Linear vs Binary — When to Use Which?

Linear Search (কখন)

  • Array unsorted
  • Very small n (≤ 20)
  • Linked list (no random access)
  • One-time search, not amortised

Binary Search (কখন)

  • Array is sorted
  • Many queries on the same data
  • Predicate is monotone
  • n is large (≥ 1000)
nLinear (worst)Binary (worst)
1001007
1,0001,00010
1,000,0001,000,00020
1,000,000,0001 billion30

7. Off-by-One Bugs & How to Avoid Them

The 3-rule discipline
  1. Define the loop invariant precisely. "answer ∈ [lo, hi]" or "answer ∈ [lo, hi)" — pick one and stick with it.
  2. Make sure the search range shrinks every iteration. If lo = mid appears, your loop may hang — add a +1 or rethink.
  3. Test boundary cases: empty array, single element, target smaller than all, target larger than all, target equal to first/last.
৩-নিয়ম শৃঙ্খলা: (১) loop invariant পরিষ্কার করে লিখুন — উত্তরের range [lo, hi] না [lo, hi); (২) প্রতি iteration-এ range যেন ছোট হয় — lo = mid দিলে infinite loop হতে পারে; (৩) boundary case test করুন — empty, single element, target সবার ছোট/বড়।

8. Practice Problems

Try first, then click to reveal the runnable solution.

প্রথমে নিজে চেষ্টা করুন; তারপর উত্তর দেখে মিলিয়ে নিন। প্রতিটি কোড directly run করা যাবে।
  1. Find the first occurrence of a target in a sorted array with duplicates.
    duplicate-সহ sorted array-এ target-এর প্রথম occurrence বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    first_occ.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int firstOcc(vector<int>& a, int x){
        int lo=0, hi=a.size(), ans=-1;
        while(lo<hi){
            int m=lo+(hi-lo)/2;
            if(a[m]==x){ ans=m; hi=m; }
            else if(a[m]<x) lo=m+1;
            else hi=m;
        }
        return ans;
    }
    int main(){
        vector<int> a={1,2,2,2,3,5};
        cout << firstOcc(a,2) << "\n";
    }
  2. Find the last occurrence of a target.
    target-এর সর্বশেষ occurrence বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    last_occ.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int lastOcc(vector<int>& a, int x){
        int lo=0, hi=a.size()-1, ans=-1;
        while(lo<=hi){
            int m=lo+(hi-lo)/2;
            if(a[m]==x){ ans=m; lo=m+1; }
            else if(a[m]<x) lo=m+1;
            else hi=m-1;
        }
        return ans;
    }
    int main(){
        vector<int> a={1,2,2,2,3,5};
        cout << lastOcc(a,2) << "\n";
    }
  3. Count occurrences of x in a sorted array in O(log n).
    sorted array-এ x-এর সংখ্যা O(log n)-এ গণনা করুন।
    ✨ Show Answer (উত্তর দেখুন)
    count.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int main(){
        vector<int> a={1,2,2,2,3,5};
        int x=2;
        auto lo = lower_bound(a.begin(),a.end(),x);
        auto hi = upper_bound(a.begin(),a.end(),x);
        cout << (hi - lo) << "\n";
    }
  4. Compute integer sqrt(n) using binary search (no cmath).
    binary search ব্যবহার করে integer sqrt(n) বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    isqrt.cpp
    #include <bits/stdc++.h>
    using namespace std;
    long long isqrt(long long n){
        long long lo=0, hi=n, ans=0;
        while(lo<=hi){
            long long m=lo+(hi-lo)/2;
            if(m*m<=n){ ans=m; lo=m+1; }
            else hi=m-1;
        }
        return ans;
    }
    int main(){ cout << isqrt(2025) << "\n"; }
  5. Find a peak in a mountain array (strictly increases then strictly decreases).
    mountain array-এ peak খুঁজে বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    peak.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int peak(vector<int>& a){
        int lo=0, hi=a.size()-1;
        while(lo<hi){
            int m=lo+(hi-lo)/2;
            if(a[m]<a[m+1]) lo=m+1;
            else hi=m;
        }
        return lo;
    }
    int main(){
        vector<int> a={1,3,7,12,9,4};
        cout << peak(a) << " -> " << a[peak(a)] << "\n";
    }
  6. Search in a rotated sorted array (no duplicates).
    rotated sorted array-এ search করুন।
    ✨ Show Answer (উত্তর দেখুন)
    rotated.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int rotSearch(vector<int>& a, int x){
        int lo=0, hi=a.size()-1;
        while(lo<=hi){
            int m=lo+(hi-lo)/2;
            if(a[m]==x) return m;
            if(a[lo]<=a[m]){
                if(a[lo]<=x && x<a[m]) hi=m-1; else lo=m+1;
            } else {
                if(a[m]<x && x<=a[hi]) lo=m+1; else hi=m-1;
            }
        }
        return -1;
    }
    int main(){
        vector<int> a={7,9,12,1,3,5};
        cout << rotSearch(a,3) << "\n";
    }
  7. Painter partition: distribute boards among k painters minimizing max paint time.
    painter partition: k জন painter-এর মধ্যে boards বিতরণ করুন যাতে সর্বাধিক সময় সর্বনিম্ন হয়।
    ✨ Show Answer (উত্তর দেখুন)
    painter.cpp
    #include <bits/stdc++.h>
    using namespace std;
    bool ok(vector<int>& b, int k, long long cap){
        long long sum=0; int cnt=1;
        for(int x:b){ if(x>cap) return false;
            if(sum+x>cap){cnt++;sum=x;} else sum+=x; }
        return cnt<=k;
    }
    int main(){
        vector<int> b={10,20,30,40}; int k=2;
        long long lo=*max_element(b.begin(),b.end());
        long long hi=accumulate(b.begin(),b.end(),0LL);
        while(lo<hi){ long long m=lo+(hi-lo)/2;
            if(ok(b,k,m)) hi=m; else lo=m+1; }
        cout << lo << "\n";
    }
  8. Find the smallest element in a rotated sorted array.
    rotated sorted array-এর সর্বনিম্ন element বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    min_rot.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int findMin(vector<int>& a){
        int lo=0, hi=a.size()-1;
        while(lo<hi){
            int m=lo+(hi-lo)/2;
            if(a[m]>a[hi]) lo=m+1;
            else hi=m;
        }
        return a[lo];
    }
    int main(){
        vector<int> a={15,18,2,3,6,12};
        cout << findMin(a) << "\n";
    }

Summary — Module 11

Linear search is O(n) and works on any data. Binary search is O(log n) but requires the data to be sorted (or a monotone predicate). The same halving idea generalises into binary search on the answer — the trick that solves problems like Allocate Books and Painter Partition. Master lower_bound and upper_bound — they appear in nearly every contest problem.

Linear search সব ডেটায় কাজ করে কিন্তু O(n)। Binary search O(log n), তবে sorted ডেটা বা monotone predicate দরকার। একই halving-idea binary search on the answer-এ কাজে আসে। lower_bound এবং upper_bound ভালোভাবে শিখে রাখুন — এগুলো প্রায় সব contest problem-এ লাগে।

Next Module → Elementary Sorting — Bubble, Selection, Insertion sort এবং stability।