Hello! Welcome to your sixth and final lesson in the "Foundational Techniques" module.
In our last lesson, we tackled searching in a rotated sorted array. The key insight was that even when the entire array isn't sorted, we can always find a sorted sub-portion at each step. By reasoning about this underlying structure, we adapted binary search to work in time.
Today, we're going to make a significant conceptual leap. We'll explore a powerful and non-obvious application of binary search where we don't search for an element in an array at all. Instead, we'll search for the answer to the problem itself within a range of possible values. This technique, often called "Binary Search on the Answer Space," is a game-changer for a specific class of optimization problems common in interviews.
This lesson directly addresses the learning outcome: Apply binary search on the answer space for optimization problems.
1. The Core Idea: Searching for an Answer, Not an Index
So far, our search space has been the indices of an array. Now, imagine the search space is the set of all possible answers to a problem. Many optimization problems can be phrased as:
- "Find the minimum value
ksuch that acondition(k)is true." - "Find the maximum value
ksuch that acondition(k)is true."
If this condition(k) has a monotonic property, we can use binary search.
To understand this crucial property, let's read a short, insightful explanation.
Powerful Ultimate Binary Search Template and Many ...
The article 'Powerful Ultimate Binary Search Template' provides a perfect summary of this concept. It introduces the idea of transforming a problem and highlights the importance of monotonicity.
Read the sections 'Most Generalized Binary Search' and 'Advanced Application' up to the first problem example. Focus on two key ideas: The generalized problem form: Minimize k, s.t. condition(k) is True. The monotonicity property: If condition(k) is True, then condition(k+1) is also True.
This monotonicity is the key. If we can find a value k for which our condition is met, we know that any "less optimal" value (e.g., k+1, k+2... for a minimization problem) will also meet the condition. This creates a sorted sequence of [False, False, ..., True, True, True] across the range of possible answers. Our goal is to find the first True.
This pattern is especially useful for problems that ask to "minimize the maximum" or "maximize the minimum". These phrases are strong signals that binary search on the answer might be the intended solution.
2. The General Algorithm
To apply binary search on the answer space, we need a systematic approach. The process generally involves three main steps:
- Define the Search Space: Determine the logical range
[low, high]for the possible answers. What is the absolute minimum the answer could be? What is the absolute maximum? - Design a
is_feasible(value)Function: This is the heart of the solution. You need to create a function that takes a potential answer (value) and checks if it's possible to satisfy the problem's constraints with that value. This check often involves a greedy approach or a simple traversal of the input data and typically runs in linear time, e.g., . - Apply Binary Search: Use the standard binary search algorithm on the range
[low, high]. In each step, use theis_feasible(mid)function to decide whether to search in the lower or upper half of the answer range.- If
is_feasible(mid)is true, it meansmidis a potential answer. We can try for an even better one. For a minimization problem, we'd try smaller values, so we sethigh = mid - 1and storemidas our current best answer. - If
is_feasible(mid)is false,midis not a valid answer. We need to aim for a less optimal value. For a minimization problem, we'd need a larger value, so we setlow = mid + 1.
- If
3. Worked Example: Koko Eating Bananas
Let's apply this framework to a classic problem: LeetCode 874. Koko Eating Bananas.
Problem Statement: Koko has n piles of bananas, piles[i]. She has h hours to eat them all. She can choose an eating speed k (bananas per hour). Each hour, she eats from a single pile. If a pile has fewer than k bananas, she eats them all and doesn't move to another pile that hour. Find the minimum integer k such that she can eat all the bananas within h hours.
This problem screams "Binary Search on the Answer"! It asks for the "minimum value k" that satisfies a condition (finishing within h hours).
The following video provides an exceptionally clear, step-by-step walkthrough of this problem. We'll go through it in sections.
First, let's understand the problem and why a simple brute-force approach is too slow. This segment from 'take U forward' explains the problem and the linear search solution.
Watch from the beginning to 06:15. This will give you a solid understanding of the problem's mechanics and the goal.
Now, let's apply our three-step algorithm.
Step 1: Define the Search Space for k
What's the slowest and fastest Koko could possibly eat?
This next clip explains how to determine the low and high bounds for our search space of possible speeds k.
Watch from 06:15 to 07:45. Pay attention to the reasoning for both the lower and upper bounds.
low= 1: The minimum possible eating speed is 1 banana per hour.high= max(piles): The maximum speed she would ever need is the size of the largest pile. Eating faster than that provides no benefit for that pile, as she still has to spend a full hour on it. We could use an even larger upper bound (likesum(piles)), but a tighter bound is more efficient.
Our search space for the answer k is [1, max(piles)].
Step 2: Design the is_feasible(speed) function
Given a potential speed k (our mid value in the binary search), can Koko finish all bananas in h hours? To check this, we simply calculate the total time required.
For each pile p with a given speed k, the time taken is ceil(p / k). In integer arithmetic, this is (p + k - 1) // k. We sum these times for all piles. If the total_time <= h, the function returns True.
This segment explains the logic for the feasibility check function.
Watch from 07:45 to 09:54. The video discusses the function to calculate the required time for a given speed.
Step 3: Apply Binary Search
Now we have all the pieces. We binary search in the range [low, high].
# Python-like Pseudocode
low = 1
high = max(piles)
min_k = high
while low <= high:
mid_k = low + (high - low) // 2
# Handle the edge case of speed being 0
if mid_k == 0:
low = 1
continue
# Step 2: Check if mid_k is a feasible speed
if is_feasible(mid_k, piles, h):
# This speed works. It's a potential answer.
# Let's try for an even smaller speed.
min_k = mid_k
high = mid_k - 1
else:
# This speed is too slow. We must increase it.
low = mid_k + 1
return min_k
Watch the final part of the video to see this logic come together with a full walkthrough and code implementation.
This extended segment puts everything together. It visualizes the binary search process, shows the full pseudocode, analyzes complexity, and points out a crucial implementation detail.
Watch from 09:54 to the end. Focus on how the low and high pointers are adjusted based on the is_feasible check. Also, note the final tip about using double or float for the ceiling calculation if you're not using the integer trick.
The time complexity will be , where N is the number of piles and M is the range of our search space (i.e., max(piles)). This is because for each step of the binary search (), we perform a linear scan over the N piles in our is_feasible function.
Test your understanding!
Consider another classic problem: LeetCode 1011. Capacity To Ship Packages Within D Days.
You have a list of package weights and must ship them in D days. You must ship them in order. What is the least weight capacity of a ship that can ship all packages within D days?
How would you apply the "Binary Search on the Answer" pattern here?
- What is the answer you are searching for?
- Define the Search Space: What are the
lowandhighbounds for the ship's capacity? - Design the
is_feasible(capacity)Function: How would you check if a givencapacityis sufficient to ship all packages inDdays?
Show answer
-
Answer: The minimum possible ship capacity.
-
Search Space:
low = max(weights): The ship must be able to carry at least the single heaviest package.high = sum(weights): In the worst case, we can use a ship with enough capacity to carry all packages in a single day.
-
is_feasible(capacity)Function:- Initialize
days_needed = 1andcurrent_weight = 0. - Iterate through the
weightsgreedily:- For each
weight, ifcurrent_weight + weight <= capacity, add it to the current day's shipment:current_weight += weight. - Otherwise, the package won't fit. We need a new day. Increment
days_needed, and setcurrent_weight = weightfor the new day.
- For each
- Return
days_needed <= D.
- Initialize
This problem is almost identical in structure to Koko Eating Bananas! You can read a detailed breakdown in the Powerful Ultimate Binary Search Template article under the "Capacity To Ship Packages Within D Days" section.
Conclusion
You've now unlocked a powerful problem-solving pattern. By reframing a problem, you can turn a complex optimization task into a straightforward binary search.
Key Takeaways:
- Binary search can be used on the "answer space," not just on arrays.
- This pattern is applicable when the problem's condition function exhibits monotonicity. If a value
kworks, any "less optimal" value also works. - Look for keywords like "minimize the maximum" or "maximize the minimum" as hints for this pattern.
- The core implementation requires three steps: defining the answer's search space, creating an
is_feasiblechecker function, and applying the binary search logic to narrow down the answer.
This concludes our module on Foundational Techniques! You've covered complexity, array traversal, and several advanced binary search patterns.
In our next lesson, we will begin a new module, Array and String Patterns, starting with a fundamental and highly versatile pattern: the two-pointer technique.