Binary Search on Answer Technique in DSA Python - Time & Space Complexity
We want to understand how fast the binary search on answer technique finds the correct answer.
How does the number of steps grow as the search range gets bigger?
Analyze the time complexity of the following code snippet.
def can_achieve(mid):
# Check if mid is a valid answer
# Returns True or False
pass
def binary_search_answer(low, high):
while low <= high:
mid = (low + high) // 2
if can_achieve(mid):
high = mid - 1
else:
low = mid + 1
return low
This code tries to find the smallest value that satisfies a condition by repeatedly checking the middle value.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The while loop that halves the search range each time.
- How many times: The loop runs about log base 2 of the size of the search range.
Each step cuts the search range in half, so the number of steps grows slowly even if the range grows a lot.
| Input Size (range size) | Approx. Operations (loop iterations) |
|---|---|
| 10 | About 4 |
| 100 | About 7 |
| 1000 | About 10 |
Pattern observation: Doubling the range adds only one more step.
Time Complexity: O(log n)
This means the number of steps grows slowly, only increasing by one each time the search range doubles.
[X] Wrong: "Binary search on answer takes linear time because it checks many values."
[OK] Correct: It does not check every value; it smartly halves the search space each time, so it only needs a few checks even for large ranges.
Understanding this technique shows you can solve problems efficiently by narrowing down answers quickly, a skill valued in many coding challenges.
What if the check function inside the loop took linear time instead of constant time? How would the overall time complexity change?
Practice
Binary Search on Answer technique?Solution
Step 1: Understand the technique's purpose
Binary Search on Answer guesses a possible answer and checks if it meets the problem's conditions.Step 2: Adjust guess based on check
If the guess is valid, we try smaller or larger guesses using binary search to find the optimal answer.Final Answer:
We guess an answer and check if it satisfies the problem, then adjust the guess using binary search. -> Option CQuick Check:
Guess and check with binary search = We guess an answer and check if it satisfies the problem, then adjust the guess using binary search. [OK]
- Confusing with normal binary search on sorted arrays
- Thinking it only finds elements, not answers
- Ignoring the check step after guessing
low and high?Solution
Step 1: Identify the binary search loop condition
Binary search typically uses a while loop with condition low <= high to cover all possible values.Step 2: Calculate mid correctly
Mid is calculated as integer division (low + high) // 2 to get the middle integer index or value.Final Answer:
while low <= high: mid = (low + high) // 2 -> Option AQuick Check:
Correct loop and mid calculation = while low <= high: mid = (low + high) // 2 [OK]
- Using for loop instead of while
- Using float division instead of integer division
- Wrong loop condition like low < high
m subarrays:
def can_split(nums, m, max_load):
count, current_sum = 1, 0
for num in nums:
if current_sum + num > max_load:
count += 1
current_sum = num
if count > m:
return False
else:
current_sum += num
return True
nums = [7, 2, 5, 10, 8]
m = 2
low, high = max(nums), sum(nums)
while low < high:
mid = (low + high) // 2
if can_split(nums, m, mid):
high = mid
else:
low = mid + 1
print(low)
What is the output of this code?Solution
Step 1: Understand the problem and initial bounds
We want to split nums into 2 subarrays minimizing the maximum sum. low = max(nums) = 10, high = sum(nums) = 32.Step 2: Trace binary search iterations
- mid=21: True ([7,2,5]=14, [10,8]=18 <=21), high=21
- mid=15: False (requires 3 subarrays), low=16
- mid=18: True, high=18
- mid=17: False, low=18
Final low=18.Final Answer:
18 -> Option BQuick Check:
Minimum max load for 2 splits = 18 [OK]
- Not setting low as max element
- Confusing count of subarrays
- Off-by-one errors in loop conditions
def can_finish(tasks, workers, max_time):
count = 1
current_time = 0
for t in tasks:
if t > max_time:
return False
if current_time + t <= max_time:
current_time += t
else:
count += 1
current_time = t
return count <= workers
tasks = [3, 6, 7, 11]
workers = 2
low, high = 0, sum(tasks)
while low < high:
mid = (low + high) // 2
if can_finish(tasks, workers, mid):
high = mid
else:
low = mid + 1
print(low)Solution
Step 1: Check initial binary search bounds
Low is set to 0, but minimum max_time must be at least max(tasks) because no task can be split.Step 2: Understand effect of wrong low
Starting low at 0 causes unnecessary checks and may cause infinite loop or wrong answer.Final Answer:
Initial low should be max(tasks), not 0 -> Option DQuick Check:
Low bound must be max task time [OK]
- Setting low to 0 instead of max(tasks)
- Wrong loop condition causing infinite loops
- Resetting current_time incorrectly
nums into k subarrays. However, some elements can be zero or negative. How should you modify the binary search on answer approach to handle this correctly?Solution
Step 1: Identify problem with negative and zero values
Negative values can reduce sums, so max(nums) may be negative or zero, and sum(nums) may be less than max(nums).Step 2: Adjust binary search bounds
Set low to minimum element (which can be negative) and high to sum of absolute values to cover all possible sums.Step 3: Modify check function
Check function must handle negative sums correctly and validate splits accordingly.Final Answer:
Set low to min(nums) and high to sum of absolute values of nums, then apply binary search with adjusted checks. -> Option AQuick Check:
Adjust bounds for negatives and zeros [OK]
- Using max(nums) as low when negatives exist
- Ignoring negative values in sums
- Assuming sums are always positive
Ready to prove it?
Pass the test and this pattern moves unknown
