Vivek
Back to Articles

Binary Search – DSA Notes

9/3/2025

Classic monotonic/partition-based binary searches.


📌 Summary Table

ProblemMonotonic / Partition IdeaBinary Search OnTimeSpace
N-th Root of MPower grows with baseBase x in [1..m]O(n · log m)O(1)
Median in Row-wise Sorted MatrixCount ≤ mid increases with midValue mid in [min..max]O(r · log c · log V)O(1)
Single Element in Sorted ArrayPair alignment flips after singleIndexO(log n)O(1)
Search in Rotated Sorted ArrayOne half is always sortedIndexO(log n)O(1)
Median of Two Sorted ArraysCorrect partition satisfies LA ≤ RB & LB ≤ RACut positionO(log(min(n,m)))O(1)
K-th Element of Two Sorted ArraysPartition with cutA + cutB = kCutAO(log(min(n,m)))O(1)
Book AllocationFeasibility non-decreasing with max pagesAnswer (max pages)O(n · log ΣA)O(1)
Aggressive Cows / Magnetic ForcePlacability increases with distanceDistanceO(n · log Range)O(1)

Notation: r,c = rows & cols; V = value range; ΣA = sum of pages; Range = max(position) - min(position).


N-th Root of M

Problem. Find an integer x such that x^n = m. If no integer root exists, return -1.

Idea. Binary search base x in [1, m]. For each mid, compute mid^n with early stop if it exceeds m to avoid overflow.

Complexity. O(n · log m) (power per step), O(1) space.

class Solution {
    // Compute a^n with early stopping once it exceeds 'limit'
    private long pow(long a, int n, long limit) {
        long r = 1;
        for (int i = 0; i < n; i++) {
            r *= a;
            if (r > limit) return limit + 1; // sentinel: "too big"
        }
        return r;
    }

    public int NthRoot(int n, int m) {
        int lo = 1, hi = m;
        while (lo <= hi) {
            int mid = lo + (hi - lo) / 2;
            long p = pow(mid, n, m);
            if (p == m) return mid;        // exact integer root
            if (p < m) lo = mid + 1;       // need bigger base
            else hi = mid - 1;             // too big
        }
        return -1; // no integer root
    }
}

Key Takeaway. Search base; use early-stop power to tame overflow.


Median in Row-wise Sorted Matrix

Problem. Each row is sorted. Find the matrix median.

Idea. Binary search on value mid between global min and max. For each mid, count how many elements ≤ mid by doing an upper-bound in every row.

Complexity. O(r · log c · log V).

class Solution {
    // Count of elements <= x in a sorted row (upper bound index)
    int countLE(int[] row, int x) {
        int lo = 0, hi = row.length - 1, ans = -1;
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            if (row[mid] <= x) { ans = mid; lo = mid + 1; }
            else hi = mid - 1;
        }
        return ans + 1;
    }

    int median(int[][] mat, int r, int c) {
        int lo = Integer.MAX_VALUE, hi = Integer.MIN_VALUE;
        // value range across rows
        for (int i = 0; i < r; i++) {
            lo = Math.min(lo, mat[i][0]);
            hi = Math.max(hi, mat[i][c - 1]);
        }

        int desired = (r * c + 1) / 2; // 1-based index of median
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            int cnt = 0;
            for (int i = 0; i < r; i++) cnt += countLE(mat[i], mid);
            if (cnt < desired) lo = mid + 1; // need larger values
            else hi = mid;                    // enough elements <= mid
        }
        return lo;
    }
}

Key Takeaway. Search value space; check count ≤ mid.


Single Element in Sorted Array (every other appears twice)

Problem. Sorted array where every element appears exactly twice except one. Return that single.

Idea. Before the single, pairs start at even indices; after it, they start at odd. Use mid ^ 1 to pair with neighbor (mid^1 flips last bit: pairs 0↔1, 2↔3, …).

Complexity. O(log n).

class Solution {
    public int singleNonDuplicate(int[] nums) {
        int lo = 0, hi = nums.length - 2; // avoid out-of-bounds on mid^1
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            if (nums[mid] == nums[mid ^ 1]) {
                lo = mid + 1;   // single is to the right
            } else {
                hi = mid - 1;   // single is at mid or to the left
            }
        }
        return nums[lo];
    }
}

Key Takeaway. Pair-index trick with mid ^ 1.


Search in Rotated Sorted Array

Problem. Find target in a rotated sorted array (distinct values).

Idea. At each step, one half is sorted. Decide which half to keep by checking if target lies inside the sorted half.

Complexity. O(log n).

class Solution {
    public int search(int[] a, int target) {
        int lo = 0, hi = a.length - 1;
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            if (a[mid] == target) return mid;

            if (a[lo] <= a[mid]) { // left half sorted
                if (a[lo] <= target && target < a[mid]) hi = mid - 1;
                else lo = mid + 1;
            } else {               // right half sorted
                if (a[mid] < target && target <= a[hi]) lo = mid + 1;
                else hi = mid - 1;
            }
        }
        return -1;
    }
}

Key Takeaway. Sorted half test narrows the search.


Median of Two Sorted Arrays (Partition)

Problem. Return median of two sorted arrays.

Idea. Binary search the cut in the smaller array so that: LA ≤ RB and LB ≤ RA. Then median is middle of max left & min right depending on parity.

Complexity. O(log(min(n, m))).

class Solution {
    public double findMedianSortedArrays(int[] A, int[] B) {
        if (A.length > B.length) return findMedianSortedArrays(B, A);
        int n = A.length, m = B.length, lo = 0, hi = n;

        while (lo <= hi) {
            int cutA = (lo + hi) >>> 1;
            int cutB = (n + m + 1) / 2 - cutA;

            int LA = (cutA == 0) ? Integer.MIN_VALUE : A[cutA - 1];
            int LB = (cutB == 0) ? Integer.MIN_VALUE : B[cutB - 1];
            int RA = (cutA == n) ? Integer.MAX_VALUE : A[cutA];
            int RB = (cutB == m) ? Integer.MAX_VALUE : B[cutB];

            if (LA <= RB && LB <= RA) {
                if (((n + m) & 1) == 0)
                    return (Math.max(LA, LB) + Math.min(RA, RB)) / 2.0;
                else
                    return Math.max(LA, LB);
            } else if (LA > RB) {
                hi = cutA - 1; // move cutA left
            } else {
                lo = cutA + 1; // move cutA right
            }
        }
        return 0.0; // unreachable for valid inputs
    }
}

Key Takeaway. Partition condition: LA ≤ RB and LB ≤ RA.


K-th Element of Two Sorted Arrays

Problem. Return the k-th smallest (1-indexed) from two sorted arrays.

Idea. Binary search cutA where cutA + cutB = k. Check the same partition inequalities.

Complexity. O(log(min(n, m))).

class Solution {
    public long kthElement(int[] A, int[] B, int n, int m, int k) {
        if (n > m) return kthElement(B, A, m, n, k); // ensure A is smaller
        int lo = Math.max(0, k - m), hi = Math.min(k, n);

        while (lo <= hi) {
            int cutA = (lo + hi) >>> 1;
            int cutB = k - cutA;

            int LA = (cutA == 0) ? Integer.MIN_VALUE : A[cutA - 1];
            int LB = (cutB == 0) ? Integer.MIN_VALUE : B[cutB - 1];
            int RA = (cutA == n) ? Integer.MAX_VALUE : A[cutA];
            int RB = (cutB == m) ? Integer.MAX_VALUE : B[cutB];

            if (LA <= RB && LB <= RA) {
                return Math.max(LA, LB);
            } else if (LA > RB) {
                hi = cutA - 1;
            } else {
                lo = cutA + 1;
            }
        }
        return -1; // invalid k
    }
}

Key Takeaway. Same partition trick as median, but stop at k.


Book Allocation (Minimize Max Pages)

Problem. Split books (array A) into M contiguous parts to minimize the maximum pages assigned to any student. If M > N, return -1.

Idea. Binary search the answer maxPages. The feasibility predicate: can we allocate with ≤ maxPages per student using at most M students?

Complexity. O(n · log ΣA).

class Solution {
    // Check if we can allocate with 'maxPages' as the per-student cap
    private static boolean can(int[] A, int students, int maxPages) {
        int need = 1, sum = 0;
        for (int x : A) {
            if (x > maxPages) return false;     // single book too large
            if (sum + x > maxPages) {           // new student
                need++;
                sum = x;
            } else {
                sum += x;
            }
        }
        return need <= students;
    }

    public static int findPages(int[] A, int N, int M) {
        if (M > N) return -1;
        int lo = 0, hi = 0;
        for (int x : A) { hi += x; lo = Math.max(lo, x); } // lo=max book, hi=sum

        int ans = -1;
        while (lo <= hi) {
            int mid = (lo + hi) >>> 1; // candidate max pages
            if (can(A, M, mid)) { ans = mid; hi = mid - 1; } // try smaller
            else lo = mid + 1;                                // need larger
        }
        return ans;
    }
}

Key Takeaway. Feasibility over answer space (classic parametric search).


Aggressive Cows / Magnetic Force

Problem. Place m cows in stalls (position[]) to maximize the minimum distance between any two cows.

Idea. Binary search distance d. Predicate canPlace(d): greedily place next cow at the earliest stall ≥ last + d.

Complexity. O(n · log Range) after sorting.

class Solution {
    // Can we place 'm' cows with at least 'dist' apart?
    private boolean canPlace(int[] pos, int m, int dist) {
        int cnt = 1, last = pos[0];  // place first cow at first stall
        for (int i = 1; i < pos.length; i++) {
            if (pos[i] - last >= dist) {
                cnt++;
                last = pos[i];
                if (cnt >= m) return true; // all cows placed
            }
        }
        return cnt >= m;
    }

    public int maxDistance(int[] position, int m) {
        Arrays.sort(position);
        int lo = 1, hi = position[position.length - 1] - position[0], ans = 0;

        while (lo <= hi) {
            int mid = (lo + hi) >>> 1;
            if (canPlace(position, m, mid)) { ans = mid; lo = mid + 1; } // try bigger d
            else hi = mid - 1;                                           // too large d
        }
        return ans;
    }
}

Key Takeaway. Greedy placement forms a monotone predicate over distance.