Searching Algorithms
"From exhaustive scanning to logarithmic bounds. Searching is the art of eliminating the irrelevant."
Searching is how every large system finds things fast. Google indexes billions of pages and returns results in milliseconds because of binary search principles at every layer. Binary Search turns an O(N) problem into O(log N) — for N = 1 billion, that's the difference between 1 billion operations and just 30. Beyond simple element search, "Binary Search on Answer Space" is one of the most powerful interview tricks — it converts problems that look impossible into clean O(N log N) solutions.
📖 Before We Start — The Big Picture
Searching is the problem of finding a target element in a dataset. The strategy depends entirely on what you know about the data:
- Unsorted data? You have no choice but Linear Search — O(N). No trick can help without first sorting.
- Sorted data? Binary Search reduces the problem by half with every comparison — O(log N). This is the cornerstone of this lecture.
- Sorted 2D matrix? You can flatten it to 1D or use Staircase search.
- Answer-space search? You don't search for a value in an array — you binary search for the optimal answer to a minimization/maximization problem.
The key insight of Binary Search is the
invariant: at every step, you maintain the guarantee that the
target — if it exists — lies within [lo, hi]. By checking
the midpoint, you eliminate half the remaining candidates. You only
need 30 checks to find any element in a 1-billion element array.
🌐 Search Overview
The goal of searching algorithms is simply to locate a target element in a dataset. Optimization comes from leveraging the properties of the dataset:
- Linear Search: The universal fallback. Scans one by one. Required when data is unsorted.
- Binary Search: The divide-and-conquer king. Slices domains in half, but requires sorted data.
- Ternary Search: Divides space into thirds. Specialized for finding peaks/valleys in unimodal functions.
- Interpolation Search: Guesses the location based on values. Incredible speed, but requires uniformly distributed data.
🔬 Algorithms Deep Dive
Linear Search
While mathematically $O(N)$ is slow, it is paradoxically the fastest search for tiny arrays ($N < 100$) due to CPU contiguous memory caching avoiding pointer jumping overhead.
Caveat - The Recursion Trap: Writing Linear Search recursively occupies $O(N)$ Space due to the Call Stack. Always write Linear Search iteratively in Java!
// Iterative: Space O(1) public int linearSearch(int[] nums, int target) { for (int i = 0; i < nums.length; i++) { if (nums[i] == target) return i; } return -1; } // Recursive: Space O(N) due to Call Stack public int recursiveLinear(int[] nums, int target, int index) { if (index >= nums.length) return -1; if (nums[index] == target) return index; return recursiveLinear(nums, target, index + 1); }
Binary Search
Systematically eliminates halves. Heavily vulnerable to infinite loops if boundaries (`low`, `high`) overlap incorrectly.
Caveat - The Overflow Bug: Calculating
mid = (low + high) / 2 overflows the 32-bit integer
limit if boundaries are massive. Always use:
mid = low + (high - low) / 2.
public int binarySearch(int[] nums, int target) { int lo = 0, hi = nums.length - 1; while (lo <= hi) { // '<=' allows exhaustive 1-element checks int mid = lo + (hi - lo) / 2; if (nums[mid] == target) return mid; if (nums[mid] < target) lo = mid + 1; else hi = mid - 1; } return -1; }
Interpolation Search
Instead of blindly checking the middle, it estimates position:
pos = lo + ((target - arr[lo]) * (hi - lo) / (arr[hi] -
arr[lo])).
Good to Know: Outperforms Binary Search by reaching $O(\log(\log N))$, but only if data is linearly uniform (e.g., 10, 20, 30...). If clumped heavily, it crashes to $O(N)$.
while (lo <= hi && target >= arr[lo] && target <= arr[hi]) { // Prevent division by zero if (lo == hi) { if (arr[lo] == target) return lo; return -1; } int pos = lo + (((hi - lo) / (arr[hi] - arr[lo])) * (target - arr[lo])); if (arr[pos] == target) return pos; if (arr[pos] < target) lo = pos + 1; else hi = pos - 1; }
Ternary Search
Calculates mid1 and mid2 to eliminate a
third of the array per pass. It is mathematically slower than Binary
Search (`2 * log_3(N) > log_2(N)`), so it is
never used for exact targeting natively.
Primary Use Case: Finding the maximum/minimum point in a continuous Unimodal function (a curve that goes strictly up, then strictly down).
while (hi - lo > 2) { int mid1 = lo + (hi - lo) / 3; int mid2 = hi - (hi - lo) / 3; // Assuming we are searching for the Maximum peak in f(x) if (f(mid1) < f(mid2)) lo = mid1; // Max must be right of mid1 else hi = mid2; // Max must be left of mid2 }
🔄 Algorithm Variations
Upper / Lower Bounds (Duplicates)
If finding the first occurrence of a duplicate,
when `nums[mid] == target`, don't stop! Keep searching left:
ans = mid; high = mid - 1; to capture the earliest
index boundary.
int ans = -1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; if (nums[mid] == target) { ans = mid; // Capture possible target hi = mid - 1; // Keep looking left for earlier occurrence } else if (nums[mid] < target) lo = mid + 1; else hi = mid - 1; }
Search in Rotated Sorted Array
If an array is shifted (`[4, 5, 6, 7, 0, 1, 2]`), you test which half is strictly monotonic. If `nums[lo] <= nums[mid]`, the left side is perfectly sorted, and you can evaluate containment safely.
if (nums[lo] <= nums[mid]) { // Left half is perfectly sorted if (target >= nums[lo] && target < nums[mid]) hi = mid - 1; // Must be in left else lo = mid + 1; // Must be in right } else { // Right half is perfectly sorted if (target > nums[mid] && target <= nums[hi]) lo = mid + 1; // Must be in right else hi = mid - 1; // Must be in left }
Binary Search on Answer Space
Searching for an abstract minimum/maximum configuration instead of an array index.
- Domain: Establish `lo` = absolute min answer, `hi` = absolute max.
- isValid(mid): Write a secondary $O(N)$ function to test if `mid` properly satisfies all constraints.
- Optimize: If `isValid(mid)` is logically valid, try optimizing further by lowering requirements: `hi = mid`. Otherwise, `lo = mid + 1`.
while (lo < hi) { int mid = lo + (hi - lo) / 2; if (isValid(mid)) { hi = mid; // It works! See if we can find a smaller valid capacity } else { lo = mid + 1; // Did not work. We must increase capacity } } return lo;
⏱️ Complexity Analysis
A global snapshot verifying Time bounds and internal memory Space usage natively.
| Algorithm | Condition | Time (Worst) | Time (Best) | Space |
|---|---|---|---|---|
| Linear Search | Unsorted | $O(N)$ | $O(1)$ | $O(1)$ |
| Binary Search | Sorted | $O(\log_2 N)$ | $O(1)$ | $O(1)$ |
| Ternary Search | Sorted / Unimodal | $O(\log_3 N)$ | $O(1)$ | $O(1)$ |
| Interpolation | Sorted, Uniform | $O(N)$ | $O(\log(\log N))$ | $O(1)$ |
🗺️ 2D Matrix Traversals
Perfectly Flattened Binary Search
If a matrix `M x C` is strictly sorted universally (the end of Row 1 is less than the start of Row 2), treat it like a 1D array of length `M * C`.
The Indexing Math:
Row Index = mid / C
Col Index = mid % C
int rows = matrix.length, cols = matrix[0].length; int lo = 0, hi = (rows * cols) - 1; while (lo <= hi) { int mid = lo + (hi - lo) / 2; int val = matrix[mid / cols][mid % cols]; if (val == target) return true; if (val < target) lo = mid + 1; else hi = mid - 1; }
Staircase / Saddleback Search
If the array is only sorted horizontally per row and vertically per col (meaning Row 2 can start with a smaller number than the end of Row 1), you cannot flatten it. You start exclusively at the Top-Right coordinate, shrinking the domain leftwards or downwards in $O(M + N)$ combinations.
int row = 0, col = matrix[0].length - 1; while (row < matrix.length && col >= 0) { if (matrix[row][col] == target) return true; if (matrix[row][col] > target) col--; // Eliminate current column else row++; // Eliminate current row }
💪 Practice Problems
📝 Topic Assignment
Complete the assignment before moving forward. It includes 8 Easy, 13 Medium, 7 Hard problems, and conceptual checks — all carefully chosen to master Searching algorithms.
📄 Open Assignment →
✅ Topic Completion Checklist
Check each item before advancing to Topic 11.
We're stepping out of contiguous arrays and stepping into dynamic node allocation! Master pointer tracking now because it will be absolutely vital for Trees and Graphs.