Your binary search on answer returns wrong answers. The binary search logic is correct, but the feasibility function has a subtle bug.
Feasibility functions are where most mistakes happen in binary search on answer problems. This guide covers the 3 most common mistakes, how to debug them, and how to verify correctness.
TL;DR
The 3 mistakes:
- Wrong monotonicity direction — Checking if we can do better than mid, not if mid works
- Off-by-one in counting — Using
pile // speedinstead of ceiling division - Incorrect greedy logic — Forgetting to update state in greedy placement
Quick fix checklist:
-
Does feasibility check if mid works (not if we can beat mid)?
-
Using ceiling division for "hours needed"?
-
Updating all state variables in greedy loops?
Mistake 1: Wrong Monotonicity Direction
The Problem
Checking if we can do better than mid, instead of checking if mid works.
❌ Wrong:
def can_finish(speed):
hours = sum((pile + speed - 1) // speed for pile in piles)
return hours < h # WRONG: should be <=Why it's wrong: We want to know if speed works, not if we can beat it.
✅ Correct:
def can_finish(speed):
hours = sum((pile + speed - 1) // speed for pile in piles)
return hours <= h # Correct: speed works if hours <= hExample
Piles: [3, 6, 7, 11], h = 8
Speed 4:
Hours = ceil(3/4) + ceil(6/4) + ceil(7/4) + ceil(11/4)
= 1 + 2 + 2 + 3 = 8
Wrong check: hours < h → 8 < 8 → False (says speed 4 doesn't work!)
Correct check: hours <= h → 8 <= 8 → True (speed 4 works)How to Fix
Ask: "Does mid satisfy the constraint?"
- Not: "Can we do better than mid?"
- Yes: "Does mid work?"
Common patterns:
hours <= h(not< h)days <= d(not< d)subarrays <= m(not< m)
Mistake 2: Off-by-One in Counting
The Problem
Using floor division when you need ceiling division.
❌ Wrong:
def can_finish(speed):
hours = sum(pile // speed for pile in piles) # WRONG
return hours <= hWhy it's wrong: pile // speed rounds down, but we need to round up (Koko needs a full hour even for partial pile).
✅ Correct:
def can_finish(speed):
hours = sum((pile + speed - 1) // speed for pile in piles) # Ceiling
return hours <= hExample
Pile: 7 bananas, Speed: 4 bananas/hour
Wrong: 7 // 4 = 1 hour (but Koko can only eat 4 in 1 hour!)
Correct: (7 + 4 - 1) // 4 = 10 // 4 = 2 hours (needs 2 hours for 7 bananas)
Verification: Hour 1: eat 4, Hour 2: eat 3 ✓Ceiling Division Formulas
Python:
import math
hours = math.ceil(pile / speed) # Method 1
hours = (pile + speed - 1) // speed # Method 2 (no import)
hours = -(-pile // speed) # Method 3 (clever trick)JavaScript:
const hours = Math.ceil(pile / speed);Java:
int hours = (pile + speed - 1) / speed;Mistake 3: Incorrect Greedy Logic
The Problem
Forgetting to update state variables in greedy algorithms.
❌ Wrong:
def can_place(min_dist):
count = 1
last_pos = position[0]
for pos in position[1:]:
if pos - last_pos >= min_dist:
count += 1
# WRONG: forgot to update last_pos!
return count >= mWhy it's wrong: We count the ball but don't update last_pos, so all future distances are measured from the first ball.
✅ Correct:
def can_place(min_dist):
count = 1
last_pos = position[0]
for pos in position[1:]:
if pos - last_pos >= min_dist:
count += 1
last_pos = pos # Update position!
return count >= mExample
Positions: [1, 2, 4, 8, 9], m = 3, min_dist = 3
Wrong logic (no update):
last_pos = 1
pos = 2: 2 - 1 = 1 < 3 → skip
pos = 4: 4 - 1 = 3 >= 3 → count = 2 (but last_pos still 1!)
pos = 8: 8 - 1 = 7 >= 3 → count = 3 (but last_pos still 1!)
pos = 9: 9 - 1 = 8 >= 3 → count = 4
Returns True (WRONG: we placed balls at 1, 4, 8, 9 but distances are wrong!)
Correct logic (with update):
last_pos = 1
pos = 2: 2 - 1 = 1 < 3 → skip
pos = 4: 4 - 1 = 3 >= 3 → count = 2, last_pos = 4
pos = 8: 8 - 4 = 4 >= 3 → count = 3, last_pos = 8
Returns True (Correct: balls at 1, 4, 8 with distances 3, 4)Common Greedy State Updates
Ship packages:
if current_load + weight > capacity:
days += 1
current_load = weight # Update load!
else:
current_load += weightSplit array:
if current_sum + num > max_sum:
subarrays += 1
current_sum = num # Update sum!
else:
current_sum += numPlace balls:
if pos - last_pos >= min_dist:
count += 1
last_pos = pos # Update position!Debugging Checklist
When your feasibility function returns wrong results:
Step 1: Verify Monotonicity (30 seconds)
Test manually:
# If mid = 5 works, does mid = 6 work?
print(is_feasible(5)) # True
print(is_feasible(6)) # Should be True for minimize maximum
# If not, your feasibility function is wrongStep 2: Check Boundary Conditions (30 seconds)
# Does the minimum answer work?
print(is_feasible(min_answer)) # Should be False or True depending on problem
# Does the maximum answer work?
print(is_feasible(max_answer)) # Should be True for minimize maximumStep 3: Trace Through Example (60 seconds)
def can_finish(speed):
hours = 0
for pile in piles:
h = (pile + speed - 1) // speed
print(f"Pile {pile}, speed {speed}: {h} hours")
hours += h
print(f"Total: {hours} hours, limit: {h}")
return hours <= h
# Trace through to find the bug
can_finish(4)Step 4: Common Mistakes Checklist
-
Using
<instead of<=(or vice versa)? -
Using floor division instead of ceiling?
-
Forgetting to update state in greedy loop?
-
Off-by-one in loop bounds?
-
Initializing counters incorrectly?
Practice Problems
Debug these feasibility functions:
Problem 1: Koko Eating Bananas
# Find the bug
def can_finish(speed):
hours = sum(pile // speed for pile in piles) # Bug!
return hours <= hProblem 2: Ship Packages
# Find the bug
def can_ship(capacity):
days = 1
current_load = 0
for weight in weights:
if current_load + weight > capacity:
days += 1
# Bug: forgot to update current_load!
else:
current_load += weight
return days <= max_daysProblem 3: Magnetic Force
# Find the bug
def can_place(min_dist):
count = 1
last_pos = position[0]
for pos in position[1:]:
if pos - last_pos > min_dist: # Bug: should be >=
count += 1
last_pos = pos
return count >= mFAQ
Q: How do I know if I need ceiling or floor division?
A: Ask: "If I have a partial unit, do I need a full time slot?" If yes, use ceiling.
Q: My feasibility function is slow. Is that ok?
A: Yes! Feasibility can be O(n) or even O(n²). Total complexity is O(log(max-min) × feasibility_cost).
Q: Should I optimize the feasibility function?
A: Only if it's a bottleneck. Correctness first, then optimize.
Q: How do I verify my feasibility function is correct?
A: Test edge cases: min_answer, max_answer, and boundary values.
Conclusion
Feasibility functions are where most binary search on answer bugs hide. The three common mistakes:
- Wrong monotonicity: Using
<instead of<= - Off-by-one counting: Using floor instead of ceiling division
- Greedy state: Forgetting to update variables
Debugging strategy:
- Verify monotonicity
- Check boundary conditions
- Trace through examples
- Use the checklist
Prevention:
- Always ask: "Does mid work?" not "Can we beat mid?"
- Use ceiling division for "time needed" calculations
- Update all state variables in greedy loops
Master these patterns and you'll write correct feasibility functions every time. For more details, see Binary Search on Answer and Minimize Maximum vs Maximize Minimum.
Want to Practice LeetCode Smarter?
LeetCopilot is a free browser extension that enhances your LeetCode practice with AI-powered hints, personalized study notes, and realistic mock interviews — all designed to accelerate your coding interview preparation.
Also compatible with Edge, Brave, and Opera
