Binary Search Applications

Level 2.5: Interview Patterns - Master binary search beyond sorted arrays for optimization problems

Advanced Binary Search Pattern Recognition
🧭 When to Use This Pattern - Checklist
Pattern Recognition Checklist

✅ Use Binary Search Applications When:

  • 🎯 Optimization Problems: Find minimum/maximum value satisfying a condition
  • 📊 Monotonic Function: As input increases, output has consistent behavior (increases/decreases)
  • 🔍 Search Space is Bounded: Clear lower and upper bounds for possible answers
  • ⚡ Efficient Validation: Can quickly check if a value satisfies the condition
  • 🎨 "At least/most" Constraints: Problems asking for minimum value that works

❌ Don't Use Binary Search When:

  • 🚫 Non-Monotonic Behavior: No clear increasing/decreasing pattern
  • 🚫 Unbounded Search Space: No clear limits on possible values
  • 🚫 Expensive Validation: Checking if value works takes too long
  • 🚫 Exact Match Required: When you need exact elements, not optimal values
🔍 Decision Flowchart:
                    ┌─────────────────────────────────┐
                    │    Problem Analysis Start       │
                    └─────────────┬───────────────────┘
                                  │
                    ┌─────────────▼───────────────────┐
                    │  Does problem ask for optimal   │
                    │  value (min/max) satisfying     │
                    │  some condition?                │
                    └─────────────┬───────────────────┘
                                  │ YES
                    ┌─────────────▼───────────────────┐
                    │  Is there a monotonic           │
                    │  relationship between input     │
                    │  and feasibility?               │
                    └─────────────┬───────────────────┘
                                  │ YES
                    ┌─────────────▼───────────────────┐
                    │  Choose Application Type:       │
                    │                                 │
                    │  🎯 SEARCH ANSWER SPACE:        │
                    │      ✓ Binary search on values  │
                    │      ✓ Check feasibility for    │
                    │        each candidate           │
                    │                                 │
                    │  📈 MINIMIZE MAXIMUM:           │
                    │      ✓ Minimize the maximum     │
                    │        value in solution        │
                    │      ✓ Often capacity/load      │
                    │        balancing problems       │
                    │                                 │
                    │  📉 MAXIMIZE MINIMUM:           │
                    │      ✓ Maximize the minimum     │
                    │        value in solution        │
                    │      ✓ Often resource           │
                    │        allocation problems      │
                    └─────────────────────────────────┘
                                
Search on Answer Space
Core Pattern: Binary Search on Result
Instead of binary searching through array indices, we binary search through possible answer values. For each candidate answer, we check if it's feasible, then adjust our search range based on the result.
🌍 Real-World Applications:
🏭 Manufacturing: Finding minimum production time to meet quota
📦 Logistics: Minimum capacity needed for shipping constraints
💾 Resource Allocation: Minimum memory needed for system requirements
🌐 Network Optimization: Minimum bandwidth for service level agreements
TypeScript
// Template for Binary Search on Answer Space
function binarySearchOnAnswer(
    minPossible: number,
    maxPossible: number,
    isValidAnswer: (candidate: number) => boolean,
    findMinimum: boolean = true
): number {
    let left = minPossible;
    let right = maxPossible;
    let result = findMinimum ? maxPossible : minPossible;
    
    while (left <= right) {
        const mid = Math.floor((left + right) / 2);
        
        if (isValidAnswer(mid)) {
            result = mid;
            if (findMinimum) {
                right = mid - 1; // Look for smaller valid answer
            } else {
                left = mid + 1;  // Look for larger valid answer
            }
        } else {
            if (findMinimum) {
                left = mid + 1;  // Need larger value
            } else {
                right = mid - 1; // Need smaller value
            }
        }
    }
    
    return result;
}

// 1. Koko Eating Bananas
function minEatingSpeed(piles: number[], h: number): number {
    const maxPile = Math.max(...piles);
    
    // Can we finish all bananas in h hours with speed k?
    const canFinish = (k: number): boolean => {
        let hoursNeeded = 0;
        for (const pile of piles) {
            hoursNeeded += Math.ceil(pile / k);
        }
        return hoursNeeded <= h;
    };
    
    return binarySearchOnAnswer(1, maxPile, canFinish, true);
}

// 2. Capacity to Ship Packages Within D Days
function shipWithinDays(weights: number[], days: number): number {
    const minCapacity = Math.max(...weights);
    const maxCapacity = weights.reduce((sum, w) => sum + w, 0);
    
    // Can we ship all packages in days with capacity?
    const canShip = (capacity: number): boolean => {
        let daysNeeded = 1;
        let currentWeight = 0;
        
        for (const weight of weights) {
            if (currentWeight + weight > capacity) {
                daysNeeded++;
                currentWeight = weight;
            } else {
                currentWeight += weight;
            }
        }
        
        return daysNeeded <= days;
    };
    
    return binarySearchOnAnswer(minCapacity, maxCapacity, canShip, true);
}

// 3. Split Array Largest Sum
function splitArray(nums: number[], k: number): number {
    const minSum = Math.max(...nums);
    const maxSum = nums.reduce((sum, num) => sum + num, 0);
    
    // Can we split array into k subarrays with max sum <= target?
    const canSplit = (target: number): boolean => {
        let subarrays = 1;
        let currentSum = 0;
        
        for (const num of nums) {
            if (currentSum + num > target) {
                subarrays++;
                currentSum = num;
            } else {
                currentSum += num;
            }
        }
        
        return subarrays <= k;
    };
    
    return binarySearchOnAnswer(minSum, maxSum, canSplit, true);
}

// 4. Find Kth Smallest in Multiplication Table
function findKthNumber(m: number, n: number, k: number): number {
    // Count how many numbers <= x in multiplication table
    const countLessEqual = (x: number): number => {
        let count = 0;
        for (let i = 1; i <= m; i++) {
            count += Math.min(Math.floor(x / i), n);
        }
        return count;
    };
    
    // Is x >= kth smallest number?
    const isKthOrLarger = (x: number): boolean => {
        return countLessEqual(x) >= k;
    };
    
    return binarySearchOnAnswer(1, m * n, isKthOrLarger, true);
}

// 5. Minimum Number of Days to Make m Bouquets
function minDays(bloomDay: number[], m: number, k: number): number {
    if (m * k > bloomDay.length) return -1;
    
    const minDay = Math.min(...bloomDay);
    const maxDay = Math.max(...bloomDay);
    
    // Can we make m bouquets by day d?
    const canMakeBouquets = (day: number): boolean => {
        let bouquets = 0;
        let consecutive = 0;
        
        for (const bloom of bloomDay) {
            if (bloom <= day) {
                consecutive++;
                if (consecutive === k) {
                    bouquets++;
                    consecutive = 0;
                }
            } else {
                consecutive = 0;
            }
        }
        
        return bouquets >= m;
    };
    
    const result = binarySearchOnAnswer(minDay, maxDay, canMakeBouquets, true);
    return result === maxDay + 1 ? -1 : result;
}

// Example usage
function demonstrateAnswerSpaceSearch(): void {
    console.log("Koko Eating Bananas:");
    console.log(minEatingSpeed([3,6,7,11], 8)); // 4
    
    console.log("\nShip Within Days:");
    console.log(shipWithinDays([1,2,3,4,5,6,7,8,9,10], 5)); // 15
    
    console.log("\nSplit Array Largest Sum:");
    console.log(splitArray([7,2,5,10,8], 2)); // 18
}
                                
Python
# Binary Search on Answer Space

def binary_search_on_answer(min_possible, max_possible, is_valid_answer, find_minimum=True):
    """Template for binary search on answer space"""
    left, right = min_possible, max_possible
    result = max_possible if find_minimum else min_possible
    
    while left <= right:
        mid = (left + right) // 2
        
        if is_valid_answer(mid):
            result = mid
            if find_minimum:
                right = mid - 1  # Look for smaller valid answer
            else:
                left = mid + 1   # Look for larger valid answer
        else:
            if find_minimum:
                left = mid + 1   # Need larger value
            else:
                right = mid - 1  # Need smaller value
    
    return result

# 1. Koko Eating Bananas
def min_eating_speed(piles, h):
    """Find minimum eating speed to finish bananas in h hours"""
    def can_finish(k):
        hours_needed = sum((pile + k - 1) // k for pile in piles)  # Ceiling division
        return hours_needed <= h
    
    return binary_search_on_answer(1, max(piles), can_finish, True)

# 2. Capacity to Ship Packages Within D Days
def ship_within_days(weights, days):
    """Find minimum ship capacity to ship all packages in days"""
    def can_ship(capacity):
        days_needed = 1
        current_weight = 0
        
        for weight in weights:
            if current_weight + weight > capacity:
                days_needed += 1
                current_weight = weight
            else:
                current_weight += weight
        
        return days_needed <= days
    
    min_capacity = max(weights)
    max_capacity = sum(weights)
    return binary_search_on_answer(min_capacity, max_capacity, can_ship, True)

# 3. Split Array Largest Sum
def split_array(nums, k):
    """Split array into k subarrays to minimize largest sum"""
    def can_split(target):
        subarrays = 1
        current_sum = 0
        
        for num in nums:
            if current_sum + num > target:
                subarrays += 1
                current_sum = num
            else:
                current_sum += num
        
        return subarrays <= k
    
    min_sum = max(nums)
    max_sum = sum(nums)
    return binary_search_on_answer(min_sum, max_sum, can_split, True)

# 4. Find Kth Smallest in Multiplication Table
def find_kth_number(m, n, k):
    """Find kth smallest number in m x n multiplication table"""
    def count_less_equal(x):
        count = 0
        for i in range(1, m + 1):
            count += min(x // i, n)
        return count
    
    def is_kth_or_larger(x):
        return count_less_equal(x) >= k
    
    return binary_search_on_answer(1, m * n, is_kth_or_larger, True)

# 5. Minimum Number of Days to Make m Bouquets
def min_days(bloom_day, m, k):
    """Find minimum days to make m bouquets of k adjacent flowers"""
    if m * k > len(bloom_day):
        return -1
    
    def can_make_bouquets(day):
        bouquets = 0
        consecutive = 0
        
        for bloom in bloom_day:
            if bloom <= day:
                consecutive += 1
                if consecutive == k:
                    bouquets += 1
                    consecutive = 0
            else:
                consecutive = 0
        
        return bouquets >= m
    
    min_day = min(bloom_day)
    max_day = max(bloom_day)
    result = binary_search_on_answer(min_day, max_day, can_make_bouquets, True)
    return -1 if not can_make_bouquets(max_day) else result

# 6. Magnetic Force Between Two Balls
def max_distance(position, m):
    """Maximum minimum distance between m balls in positions"""
    position.sort()
    
    def can_place_balls(min_dist):
        count = 1  # Place first ball at position[0]
        last_pos = position[0]
        
        for pos in position[1:]:
            if pos - last_pos >= min_dist:
                count += 1
                last_pos = pos
                if count == m:
                    return True
        return False
    
    min_dist = 1
    max_dist = position[-1] - position[0]
    return binary_search_on_answer(min_dist, max_dist, can_place_balls, False)

# Example usage
def demonstrate_answer_space_search():
    print("Koko Eating Bananas:")
    print(min_eating_speed([3, 6, 7, 11], 8))  # 4
    
    print("\nShip Within Days:")
    print(ship_within_days([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 5))  # 15
    
    print("\nSplit Array Largest Sum:")
    print(split_array([7, 2, 5, 10, 8], 2))  # 18
    
    print("\nMagnetic Force Between Balls:")
    print(max_distance([1, 2, 3, 4, 7], 3))  # 3
                                
OCaml
open Printf

(* Binary Search on Answer Space Template *)
let binary_search_on_answer min_possible max_possible is_valid_answer find_minimum =
  let left = ref min_possible in
  let right = ref max_possible in
  let result = ref (if find_minimum then max_possible else min_possible) in
  
  while !left <= !right do
    let mid = (!left + !right) / 2 in
    
    if is_valid_answer mid then begin
      result := mid;
      if find_minimum then
        right := mid - 1  (* Look for smaller valid answer *)
      else
        left := mid + 1   (* Look for larger valid answer *)
    end else begin
      if find_minimum then
        left := mid + 1   (* Need larger value *)
      else
        right := mid - 1  (* Need smaller value *)
    end
  done;
  
  !result

(* 1. Koko Eating Bananas *)
let min_eating_speed piles h =
  let max_pile = Array.fold_left max 0 piles in
  
  let can_finish k =
    let hours_needed = Array.fold_left (fun acc pile ->
      acc + ((pile + k - 1) / k)  (* Ceiling division *)
    ) 0 piles in
    hours_needed <= h
  in
  
  binary_search_on_answer 1 max_pile can_finish true

(* 2. Capacity to Ship Packages Within D Days *)
let ship_within_days weights days =
  let min_capacity = Array.fold_left max 0 weights in
  let max_capacity = Array.fold_left (+) 0 weights in
  
  let can_ship capacity =
    let days_needed = ref 1 in
    let current_weight = ref 0 in
    
    Array.iter (fun weight ->
      if !current_weight + weight > capacity then begin
        incr days_needed;
        current_weight := weight
      end else
        current_weight := !current_weight + weight
    ) weights;
    
    !days_needed <= days
  in
  
  binary_search_on_answer min_capacity max_capacity can_ship true

(* 3. Split Array Largest Sum *)
let split_array nums k =
  let min_sum = Array.fold_left max 0 nums in
  let max_sum = Array.fold_left (+) 0 nums in
  
  let can_split target =
    let subarrays = ref 1 in
    let current_sum = ref 0 in
    
    Array.iter (fun num ->
      if !current_sum + num > target then begin
        incr subarrays;
        current_sum := num
      end else
        current_sum := !current_sum + num
    ) nums;
    
    !subarrays <= k
  in
  
  binary_search_on_answer min_sum max_sum can_split true

(* 4. Minimum Days to Make Bouquets *)
let min_days bloom_day m k =
  let n = Array.length bloom_day in
  if m * k > n then -1
  else
    let min_day = Array.fold_left min max_int bloom_day in
    let max_day = Array.fold_left max 0 bloom_day in
    
    let can_make_bouquets day =
      let bouquets = ref 0 in
      let consecutive = ref 0 in
      
      Array.iter (fun bloom ->
        if bloom <= day then begin
          incr consecutive;
          if !consecutive = k then begin
            incr bouquets;
            consecutive := 0
          end
        end else
          consecutive := 0
      ) bloom_day;
      
      !bouquets >= m
    in
    
    if can_make_bouquets max_day then
      binary_search_on_answer min_day max_day can_make_bouquets true
    else -1

(* Example usage *)
let demonstrate_answer_space_search () =
  printf "Koko Eating Bananas: %d\n" 
    (min_eating_speed [|3; 6; 7; 11|] 8);
  
  printf "Ship Within Days: %d\n" 
    (ship_within_days [|1; 2; 3; 4; 5; 6; 7; 8; 9; 10|] 5);
  
  printf "Split Array Largest Sum: %d\n" 
    (split_array [|7; 2; 5; 10; 8|] 2)
                                
Problem Type Time Complexity Space Complexity Key Insight
Answer Space Search O(log(max-min) × validation) O(1) Binary search on possible answers
Capacity Problems O(n × log(sum)) O(1) Greedy validation with capacity limit
Array Splitting O(n × log(sum)) O(1) Count subarrays with sum constraint
Optimization Problems
Finding Optimal Values with Constraints
These problems involve finding the best possible value (minimum or maximum) that satisfies all given constraints. The key is identifying the monotonic property that allows binary search.
📈 Optimization Pattern:
MINIMIZE MAXIMUM Pattern:
Problem: Split array into K subarrays, minimize the maximum sum

Monotonic Property:
- If we can split with max sum X, we can also split with max sum > X
- If we can't split with max sum X, we can't split with max sum < X

Binary Search Range: [max(arr), sum(arr)]

MAXIMIZE MINIMUM Pattern:
Problem: Place M balls to maximize minimum distance

Monotonic Property:
- If we can place with min distance X, we can place with min distance < X  
- If we can't place with min distance X, we can't place with min distance > X

Binary Search Range: [1, max_distance]

Template Recognition:
┌─ "Minimize the maximum..." → Binary search for minimum feasible maximum
├─ "Maximize the minimum..." → Binary search for maximum feasible minimum
├─ "At least/most K..." → Binary search with constraint validation
└─ "Find smallest/largest X such that..." → Binary search on answer space
                                
TypeScript
// 1. Minimize Maximum - Painter's Partition Problem
function paintersPartition(boards: number[], painters: number): number {
    const minTime = Math.max(...boards);
    const maxTime = boards.reduce((sum, board) => sum + board, 0);
    
    // Can we paint all boards with painters in time?
    const canPaint = (time: number): boolean => {
        let paintersUsed = 1;
        let currentTime = 0;
        
        for (const board of boards) {
            if (currentTime + board > time) {
                paintersUsed++;
                currentTime = board;
            } else {
                currentTime += board;
            }
        }
        
        return paintersUsed <= painters;
    };
    
    return binarySearchOnAnswer(minTime, maxTime, canPaint, true);
}

// 2. Maximize Minimum - Aggressive Cows
function aggressiveCows(stalls: number[], cows: number): number {
    stalls.sort((a, b) => a - b);
    const minDistance = 1;
    const maxDistance = stalls[stalls.length - 1] - stalls[0];
    
    // Can we place cows with minimum distance d?
    const canPlaceCows = (distance: number): boolean => {
        let placed = 1;
        let lastPosition = stalls[0];
        
        for (let i = 1; i < stalls.length; i++) {
            if (stalls[i] - lastPosition >= distance) {
                placed++;
                lastPosition = stalls[i];
                if (placed === cows) return true;
            }
        }
        
        return false;
    };
    
    return binarySearchOnAnswer(minDistance, maxDistance, canPlaceCows, false);
}

// 3. Book Allocation Problem
function allocateBooks(books: number[], students: number): number {
    if (students > books.length) return -1;
    
    const minPages = Math.max(...books);
    const maxPages = books.reduce((sum, pages) => sum + pages, 0);
    
    // Can we allocate books such that max pages <= limit?
    const canAllocate = (limit: number): boolean => {
        let studentsUsed = 1;
        let currentPages = 0;
        
        for (const pageCount of books) {
            if (currentPages + pageCount > limit) {
                studentsUsed++;
                currentPages = pageCount;
            } else {
                currentPages += pageCount;
            }
        }
        
        return studentsUsed <= students;
    };
    
    return binarySearchOnAnswer(minPages, maxPages, canAllocate, true);
}

// 4. Minimum Speed to Arrive on Time
function minSpeedOnTime(dist: number[], hour: number): number {
    const n = dist.length;
    
    // Need at least n-1 hours for n-1 trains + some time for last train
    if (hour <= n - 1) return -1;
    
    const maxSpeed = 10000000; // Given constraint
    
    // Can we reach on time with speed s?
    const canReachOnTime = (speed: number): boolean => {
        let totalTime = 0;
        
        // First n-1 trains: need integer hours (ceiling)
        for (let i = 0; i < n - 1; i++) {
            totalTime += Math.ceil(dist[i] / speed);
        }
        
        // Last train: can use fractional time
        totalTime += dist[n - 1] / speed;
        
        return totalTime <= hour;
    };
    
    const result = binarySearchOnAnswer(1, maxSpeed, canReachOnTime, true);
    return result > maxSpeed ? -1 : result;
}

// 5. Cutting Ribbons
function maxLength(ribbons: number[], k: number): number {
    if (k === 0) return 0;
    
    const maxRibbon = Math.max(...ribbons);
    
    // Can we cut k pieces of length len?
    const canCut = (length: number): boolean => {
        let pieces = 0;
        for (const ribbon of ribbons) {
            pieces += Math.floor(ribbon / length);
            if (pieces >= k) return true;
        }
        return false;
    };
    
    return binarySearchOnAnswer(1, maxRibbon, canCut, false);
}

// Example usage
function demonstrateOptimization(): void {
    console.log("Painter's Partition:");
    console.log(paintersPartition([10, 20, 30, 40], 2)); // 60
    
    console.log("\nAggressive Cows:");
    console.log(aggressiveCows([1, 2, 4, 8, 9], 3)); // 3
    
    console.log("\nBook Allocation:");
    console.log(allocateBooks([12, 34, 67, 90], 2)); // 113
}
                                
Python
# Optimization Problems with Binary Search

# 1. Minimize Maximum - Painter's Partition Problem
def painters_partition(boards, painters):
    """Minimize maximum time to paint all boards with given painters"""
    def can_paint(time):
        painters_used = 1
        current_time = 0
        
        for board in boards:
            if current_time + board > time:
                painters_used += 1
                current_time = board
            else:
                current_time += board
        
        return painters_used <= painters
    
    min_time = max(boards)
    max_time = sum(boards)
    return binary_search_on_answer(min_time, max_time, can_paint, True)

# 2. Maximize Minimum - Aggressive Cows
def aggressive_cows(stalls, cows):
    """Maximize minimum distance between cows"""
    stalls.sort()
    
    def can_place_cows(distance):
        placed = 1
        last_position = stalls[0]
        
        for i in range(1, len(stalls)):
            if stalls[i] - last_position >= distance:
                placed += 1
                last_position = stalls[i]
                if placed == cows:
                    return True
        return False
    
    min_distance = 1
    max_distance = stalls[-1] - stalls[0]
    return binary_search_on_answer(min_distance, max_distance, can_place_cows, False)

# 3. Book Allocation Problem
def allocate_books(books, students):
    """Minimize maximum pages allocated to any student"""
    if students > len(books):
        return -1
    
    def can_allocate(limit):
        students_used = 1
        current_pages = 0
        
        for pages in books:
            if current_pages + pages > limit:
                students_used += 1
                current_pages = pages
            else:
                current_pages += pages
        
        return students_used <= students
    
    min_pages = max(books)
    max_pages = sum(books)
    return binary_search_on_answer(min_pages, max_pages, can_allocate, True)

# 4. Minimum Speed to Arrive on Time
def min_speed_on_time(dist, hour):
    """Find minimum speed to reach destination on time"""
    n = len(dist)
    
    # Need at least n-1 hours for first n-1 trains
    if hour <= n - 1:
        return -1
    
    def can_reach_on_time(speed):
        total_time = 0
        
        # First n-1 trains need integer hours (ceiling)
        for i in range(n - 1):
            total_time += math.ceil(dist[i] / speed)
        
        # Last train can use fractional time
        total_time += dist[n - 1] / speed
        
        return total_time <= hour
    
    import math
    max_speed = 10**7  # Given constraint
    result = binary_search_on_answer(1, max_speed, can_reach_on_time, True)
    return -1 if result > max_speed else result

# 5. Cutting Ribbons
def max_length(ribbons, k):
    """Find maximum length of k equal pieces from ribbons"""
    if k == 0:
        return 0
    
    def can_cut(length):
        pieces = sum(ribbon // length for ribbon in ribbons)
        return pieces >= k
    
    max_ribbon = max(ribbons)
    return binary_search_on_answer(1, max_ribbon, can_cut, False)

# 6. Minimize Maximum Distance to Gas Station
def min_max_gas_dist(stations, k):
    """Add k gas stations to minimize maximum distance between consecutive stations"""
    distances = []
    for i in range(1, len(stations)):
        distances.append(stations[i] - stations[i-1])
    
    def can_achieve_max_dist(max_dist):
        stations_needed = 0
        for dist in distances:
            # Number of stations needed in this segment
            stations_needed += int(dist / max_dist)
        return stations_needed <= k
    
    left, right = 0, max(distances)
    epsilon = 1e-6
    
    while right - left > epsilon:
        mid = (left + right) / 2
        if can_achieve_max_dist(mid):
            right = mid
        else:
            left = mid
    
    return left

# Example usage
def demonstrate_optimization():
    print("Painter's Partition:")
    print(painters_partition([10, 20, 30, 40], 2))  # 60
    
    print("\nAggressive Cows:")
    print(aggressive_cows([1, 2, 4, 8, 9], 3))  # 3
    
    print("\nBook Allocation:")
    print(allocate_books([12, 34, 67, 90], 2))  # 113
                                
OCaml
open Printf

(* Optimization Problems with Binary Search *)

(* 1. Painter's Partition Problem *)
let painters_partition boards painters =
  let min_time = Array.fold_left max 0 boards in
  let max_time = Array.fold_left (+) 0 boards in
  
  let can_paint time =
    let painters_used = ref 1 in
    let current_time = ref 0 in
    
    Array.iter (fun board ->
      if !current_time + board > time then begin
        incr painters_used;
        current_time := board
      end else
        current_time := !current_time + board
    ) boards;
    
    !painters_used <= painters
  in
  
  binary_search_on_answer min_time max_time can_paint true

(* 2. Aggressive Cows *)
let aggressive_cows stalls cows =
  Array.sort compare stalls;
  let n = Array.length stalls in
  
  let can_place_cows distance =
    let placed = ref 1 in
    let last_position = ref stalls.(0) in
    let found = ref false in
    
    for i = 1 to n - 1 do
      if not !found && stalls.(i) - !last_position >= distance then begin
        incr placed;
        last_position := stalls.(i);
        if !placed = cows then found := true
      end
    done;
    
    !found
  in
  
  let min_distance = 1 in
  let max_distance = stalls.(n-1) - stalls.(0) in
  binary_search_on_answer min_distance max_distance can_place_cows false

(* 3. Book Allocation Problem *)
let allocate_books books students =
  let n = Array.length books in
  if students > n then -1
  else
    let min_pages = Array.fold_left max 0 books in
    let max_pages = Array.fold_left (+) 0 books in
    
    let can_allocate limit =
      let students_used = ref 1 in
      let current_pages = ref 0 in
      
      Array.iter (fun pages ->
        if !current_pages + pages > limit then begin
          incr students_used;
          current_pages := pages
        end else
          current_pages := !current_pages + pages
      ) books;
      
      !students_used <= students
    in
    
    binary_search_on_answer min_pages max_pages can_allocate true

(* 4. Cutting Ribbons *)
let max_length ribbons k =
  if k = 0 then 0
  else
    let max_ribbon = Array.fold_left max 0 ribbons in
    
    let can_cut length =
      let pieces = Array.fold_left (fun acc ribbon ->
        acc + (ribbon / length)
      ) 0 ribbons in
      pieces >= k
    in
    
    binary_search_on_answer 1 max_ribbon can_cut false

(* Example usage *)
let demonstrate_optimization () =
  printf "Painter's Partition: %d\n" 
    (painters_partition [|10; 20; 30; 40|] 2);
  
  printf "Aggressive Cows: %d\n" 
    (aggressive_cows [|1; 2; 4; 8; 9|] 3);
  
  printf "Book Allocation: %d\n" 
    (allocate_books [|12; 34; 67; 90|] 2)
                                
Problem Pattern Time Complexity Space Complexity Key Insight
Minimize Maximum O(n × log(sum)) O(1) Greedy partitioning with limit
Maximize Minimum O(n log n + n × log(max_dist)) O(1) Greedy placement with distance constraint
Resource Allocation O(n × log(sum)) O(1) Sequential allocation with capacity
🎯 Interview Tips & Pattern Recognition
Interview Strategy Guide

🔍 Recognizing Binary Search Applications:

  • Keywords to Watch: "minimum", "maximum", "optimize", "at least", "at most", "smallest X such that", "largest Y such that"
  • Problem Structure: Decision problem + optimization = binary search candidate
  • Monotonic Check: If X works, does X+1 work? If X doesn't work, does X-1 work?
  • Bounds Identification: What are the minimum and maximum possible answer values?

💡 Implementation Strategy:

  • Step 1: Convert optimization to decision problem ("Can we do X with value Y?")
  • Step 2: Identify search bounds (min possible answer, max possible answer)
  • Step 3: Implement validation function efficiently
  • Step 4: Apply binary search template with proper bound updates

⚠️ Common Pitfalls:

  • Wrong Monotonic Direction: Ensure you understand if larger values make problem easier/harder
  • Off-by-One Errors: Be careful with <= vs < in validation and bound updates
  • Integer Overflow: Watch for overflow when calculating mid = (left + right) / 2
  • Floating Point Precision: Use epsilon for problems requiring decimal answers
  • Edge Cases: Handle empty inputs, impossible constraints, single element arrays
🌟 Interview Success Framework:
📋 Problem Analysis: "Given this optimization problem, can I convert it to 'is X achievable?'"
🧪 Monotonic Test: "If I can do X, can I do X+1? If not X, then not X-1?"
📐 Bounds Setting: "What's the worst case (upper bound) and best case (lower bound)?"
⚡ Validation Design: "How quickly can I check if a candidate answer works?"
🎯 Implementation: "Apply binary search template with careful bound management"