Modified Binary Search Patterns

Matrix Type Strategy Complexity Key Insight
Fully Sorted Treat as 1D array, use binary search with index mapping O(log(m×n)) map index i to [i/n][i%n]
Row & Column Sorted Start from top-right or bottom-left, eliminate row or column O(m + n) Staircase search pattern
Row Sorted Only Binary search on each row O(m log n) m independent searches
First Column Sorted Binary search rows, then binary search within row O(log m + log n) Two-level binary search

Example: Search in fully sorted 2D matrix

function searchMatrix(matrix, target) {
    if (!matrix.length || !matrix[0].length) {
        return false;
    }
    
    const m = matrix.length;
    const n = matrix[0].length;
    let left = 0;
    let right = m * n - 1;
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        
        // Convert 1D index to 2D coordinates
        const row = Math.floor(mid / n);
        const col = mid % n;
        const midVal = matrix[row][col];
        
        if (midVal === target) {
            return true;
        } else if (midVal < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return false;
}

// Matrix: [[1,3,5,7],[10,11,16,20],[23,30,34,60]]
// Treat as: [1,3,5,7,10,11,16,20,23,30,34,60]

Example: Search in row/col sorted matrix

function searchMatrixII(matrix, target) {
    if (!matrix.length || !matrix[0].length) {
        return false;
    }
    
    const m = matrix.length;
    const n = matrix[0].length;
    
    // Start from top-right corner
    let row = 0;
    let col = n - 1;
    
    while (row < m && col >= 0) {
        const val = matrix[row][col];
        
        if (val === target) {
            return true;
        } else if (val > target) {
            col--; // Move left (smaller values)
        } else {
            row++; // Move down (larger values)
        }
    }
    
    return false;
}

// Matrix: [[1,4,7,11],[2,5,8,12],[3,6,9,16]]
// Each row sorted, each column sorted
// Staircase search from top-right

2. Find Peak Element Problem

Property Technique Condition Guarantee
Peak Definition Element greater than neighbors nums[i] > nums[i-1] && nums[i] > nums[i+1] At least one peak exists
Binary Search Move toward higher neighbor If nums[mid] < nums[mid+1], go right Always converges to peak
Edge Cases Treat out-of-bounds as -∞ First/last elements can be peaks No special handling needed
Multiple Peaks Any peak is valid answer Binary search finds one peak O(log n) solution

Example: Find peak element

function findPeakElement(nums) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        
        // Compare with right neighbor
        if (nums[mid] < nums[mid + 1]) {
            // Peak is on the right (ascending)
            left = mid + 1;
        } else {
            // Peak is on the left or at mid (descending)
            right = mid;
        }
    }
    
    return left; // or right, they're equal
}

// Example: [1,2,3,1] -> return 2 (index of peak 3)
// Example: [1,2,1,3,5,6,4] -> return 1 or 5

// Why this works:
// - If mid < mid+1, peak must be on right (we're ascending)
// - If mid > mid+1, peak could be mid or on left
// - Loop terminates when left === right (found peak)
Note: Peak finding works because: if we're at position where right neighbor is higher, moving right guarantees we'll reach a peak (either continues ascending to end, or descends at some point creating a peak).

3. Search in Rotated Sorted Array

Scenario Identification Action Example
Left Half Sorted nums[left] <= nums[mid] Check if target in sorted half, else search other half [4,5,6,7,0,1,2] mid=7
Right Half Sorted nums[mid] < nums[right] Check if target in sorted half, else search other half [4,5,6,7,0,1,2] mid=0
Target in Sorted Half Compare target with boundaries Binary search in that half Target=5 in [4,5,6,7]
Target in Unsorted Half Other half must contain rotation Search in unsorted half Target=1 in [0,1,2]

Example: Search in rotated sorted array

function searchRotated(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        
        if (nums[mid] === target) {
            return mid;
        }
        
        // Determine which half is sorted
        if (nums[left] <= nums[mid]) {
            // Left half is sorted
            if (nums[left] <= target && target < nums[mid]) {
                // Target in left sorted half
                right = mid - 1;
            } else {
                // Target in right half
                left = mid + 1;
            }
        } else {
            // Right half is sorted
            if (nums[mid] < target && target <= nums[right]) {
                // Target in right sorted half
                left = mid + 1;
            } else {
                // Target in left half
                right = mid - 1;
            }
        }
    }
    
    return -1;
}

// Example: nums = [4,5,6,7,0,1,2], target = 0
// Output: 4
// Rotation point between 7 and 0

4. Find Minimum in Rotated Sorted Array

Pattern Condition Decision Invariant
No Rotation nums[left] < nums[right] Return nums[left] Already sorted
Mid in Left Part nums[mid] > nums[right] Minimum in right half: left = mid + 1 Rotation point on right
Mid in Right Part nums[mid] <= nums[right] Minimum in left half or at mid: right = mid Rotation point on left or mid
With Duplicates nums[mid] === nums[right] Shrink: right-- Worst case O(n)

Example: Find minimum (no duplicates)

function findMin(nums) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left < right) {
        // Already sorted (no rotation in this range)
        if (nums[left] < nums[right]) {
            return nums[left];
        }
        
        const mid = Math.floor((left + right) / 2);
        
        if (nums[mid] > nums[right]) {
            // Min is in right half
            left = mid + 1;
        } else {
            // Min is in left half or at mid
            right = mid;
        }
    }
    
    return nums[left];
}

// Example: [3,4,5,1,2] -> 1
// Example: [4,5,6,7,0,1,2] -> 0
// Example: [11,13,15,17] -> 11 (no rotation)

Example: Find minimum (with duplicates)

function findMinDuplicates(nums) {
    let left = 0;
    let right = nums.length - 1;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        
        if (nums[mid] > nums[right]) {
            // Min is in right half
            left = mid + 1;
        } else if (nums[mid] < nums[right]) {
            // Min is in left half or at mid
            right = mid;
        } else {
            // nums[mid] === nums[right]
            // Can't determine which half, shrink right
            right--;
        }
    }
    
    return nums[left];
}

// Example: [2,2,2,0,1] -> 0
// Example: [1,3,5] -> 1
// Duplicates require O(n) worst case

5. Search Insert Position in Array

Variant Return Value Condition Use Case
Insert Position Index where target should be inserted First position where nums[i] >= target Maintain sorted order
Lower Bound First occurrence of target Leftmost position with nums[i] >= target Find start of range
Upper Bound First element greater than target Leftmost position with nums[i] > target Find end of range + 1
Exact Match Index if found, -1 otherwise Traditional binary search Element lookup

Example: Search insert position (lower bound)

function searchInsert(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    
    return left;
}

// Example: nums = [1,3,5,6], target = 5 -> 2
// Example: nums = [1,3,5,6], target = 2 -> 1
// Example: nums = [1,3,5,6], target = 7 -> 4
// Example: nums = [1,3,5,6], target = 0 -> 0

// Pattern: Find leftmost position where nums[i] >= target
// If target exists, returns its first occurrence
// If target doesn't exist, returns where it should be inserted

Example: Lower and upper bound templates

// Lower bound: first position where nums[i] >= target
function lowerBound(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

// Upper bound: first position where nums[i] > target
function upperBound(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        if (nums[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

// Usage examples:
const nums = [1, 2, 2, 2, 3, 4, 5];
console.log(lowerBound(nums, 2)); // 1 (first 2)
console.log(upperBound(nums, 2)); // 4 (first element > 2)
// Range of 2's: [lowerBound, upperBound)

6. Find First and Last Position in Sorted Array

Task Approach Technique Complexity
Find First Binary search for leftmost occurrence When found, continue searching left O(log n)
Find Last Binary search for rightmost occurrence When found, continue searching right O(log n)
Two Binary Searches Run two independent searches Lower bound and upper bound - 1 O(log n)
No Match Return [-1, -1] Check if target exists at found position Handle edge case

Example: Find first and last position

function searchRange(nums, target) {
    const first = findFirst(nums, target);
    const last = findLast(nums, target);
    return [first, last];
}

function findFirst(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    let result = -1;
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        
        if (nums[mid] === target) {
            result = mid;
            right = mid - 1; // Continue searching left
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return result;
}

function findLast(nums, target) {
    let left = 0;
    let right = nums.length - 1;
    let result = -1;
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        
        if (nums[mid] === target) {
            result = mid;
            left = mid + 1; // Continue searching right
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return result;
}

// Example: nums = [5,7,7,8,8,10], target = 8
// Output: [3, 4]
// Example: nums = [5,7,7,8,8,10], target = 6
// Output: [-1, -1]

Example: Using lower/upper bound approach

function searchRangeBounds(nums, target) {
    const left = lowerBound(nums, target);
    const right = upperBound(nums, target) - 1;
    
    // Check if target exists
    if (left < nums.length && nums[left] === target) {
        return [left, right];
    }
    return [-1, -1];
}

function lowerBound(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

function upperBound(nums, target) {
    let left = 0;
    let right = nums.length;
    
    while (left < right) {
        const mid = Math.floor((left + right) / 2);
        if (nums[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return left;
}

// Cleaner approach using standard bound functions
// First = lowerBound, Last = upperBound - 1
Warning: Common binary search pitfalls:
  • Infinite loop: Use left < right vs left <= right appropriately based on loop variant
  • Overflow: Use left + Math.floor((right - left) / 2) instead of (left + right) / 2
  • Off-by-one: Carefully choose mid + 1 or mid - 1 vs mid
  • Boundary conditions: Test with empty array, single element, duplicates, target at edges

Summary: Modified Binary Search Patterns

  • 2D Matrix Search: Fully sorted treats as 1D with index mapping; row/col sorted uses staircase from top-right O(m+n)
  • Peak Element: Move toward higher neighbor - if mid < mid+1 go right, else go left; always converges to peak O(log n)
  • Rotated Array Search: Determine which half is sorted (nums[left] ≤ nums[mid] or nums[mid] < nums[right]), check if target in sorted half
  • Find Minimum Rotated: If nums[mid] > nums[right], minimum in right half; else minimum in left or at mid; O(log n)
  • Duplicates in Rotated: When nums[mid] == nums[right], must shrink right-- resulting in worst case O(n)
  • Insert Position: Lower bound pattern - first position where nums[i] ≥ target using left < right loop
  • Lower/Upper Bound: Lower: first ≥ target; Upper: first > target; use right = nums.length to handle insertion at end
  • First/Last Position: Two binary searches: find first by continuing left when found, find last by continuing right when found
  • Bounds Alternative: First position = lowerBound, Last position = upperBound - 1, check if target exists
  • Loop Variants: left < right with right = mid or left <= right with right = mid - 1 - be consistent
  • Key Principle: Maintain invariant that answer is always in [left, right], eliminate half of search space each iteration