The problem
You have versions 1, 2, …, n of a product. At some point one version went bad, and every version after a bad one is also bad. You can call isBad(v), which says whether version v is bad. Return the first bad version, using as few calls as possible.
In the code below, the API is simulated by passing the real answer bad and defining isBad(v) as v >= bad.
Input: n = 5, first bad version = 4
Output: 4Versions 1 to 3 are good, versions 4 and 5 are bad, so the first bad one is 4.
How to think about it
Start with brute force. Call isBad(1), isBad(2), … until one is bad. That is O(n) calls. With n up to 2³¹ − 1, that could be two billion API calls.
Look at the shape of the answers. Read isBad across all versions and you get good, good, …, good, bad, bad, …, bad. Once it flips, it never flips back. That is a monotonic yes/no sequence, and the answer is the position of the first yes.
The key insight: test the middle and keep the half that still contains the boundary. If mid is bad, the first bad version is mid or earlier, so set hi = mid (don’t throw mid away; it might be the answer). If mid is good, the first bad version is strictly later, so set lo = mid + 1. Stop when lo == hi: that single remaining version is the answer.
Which pattern is this? Binary search on a boundary, the “first true” template with while lo < hi. The tell is any monotonic predicate: false then true, and you want the switch point. This template reappears in Koko Eating Bananas and every “search on the answer” problem.
Approach
- Set
lo = 1andhi = n. The first bad version is somewhere in[lo, hi]. - While
lo < hi:mid = (lo + hi) // 2.- If
isBad(mid), sethi = mid. - Else set
lo = mid + 1.
- Return
lo.
Solution
def first_bad_version(n, bad):
def isBad(v): return v >= bad # given by the API in the real problem
lo, hi = 1, n
while lo < hi: # stop when one candidate remains
mid = (lo + hi) // 2
if isBad(mid): hi = mid # mid could be the first bad one: keep it
else: lo = mid + 1 # mid is good: answer is after it
return lo
# first_bad_version(5, 4) -> 4Dry run
On n = 5, first bad version 4:
| Step | lo |
hi |
mid |
isBad(mid) |
Decision |
|---|---|---|---|---|---|
| 1 | 1 | 5 | 3 | False | lo = 4 |
| 2 | 4 | 5 | 4 | True | hi = 4 |
Now lo == hi == 4, so the answer is 4, found with two API calls.
Complexity
- Time: O(log n). Each call halves the range of candidates, so about 31 calls cover
n = 2³¹ − 1. - Space: O(1).
Edge cases and pitfalls
n = 1: the loop never runs and the answer is 1 (the problem guarantees a bad version exists).- First version is bad:
hikeeps shrinking to 1. - Last version is bad:
lokeeps climbing ton. hi = mid - 1whenmidis bad can skip the answer. In the boundary template, the side that may contain the answer keepsmid.while lo <= hiwithhi = midloops forever oncelo == hi. Uselo < hiwith this template.- Overflow in other languages: compute
lo + (hi - lo) // 2, sincelo + hican exceed 2³¹ − 1.
Related problems
- Search Insert Position (LeetCode 35): the same first-true boundary, with ”
nums[i] >= target” as the test. - Koko Eating Bananas (LeetCode 875): the test is “can she finish at this speed?” and you search over speeds.
- Find Minimum in Rotated Sorted Array (LeetCode 153): the same
lo < hi,hi = midtemplate on a rotated array.
- 1.Binary Search (LeetCode 704)
- 2.Search Insert Position (LeetCode 35)
- 3.First Bad Version (LeetCode 278)
- 4.Search in Rotated Sorted Array (LeetCode 33)
- 5.Find Minimum in Rotated Sorted Array (LeetCode 153)
- 6.Find First and Last Position of Element in Sorted Array (LeetCode 34)
- 7.Search a 2D Matrix (LeetCode 74)
- 8.Find Peak Element (LeetCode 162)
- 9.Koko Eating Bananas (LeetCode 875)
- 10.Capacity To Ship Packages Within D Days (LeetCode 1011)
- 11.Time Based Key-Value Store (LeetCode 981)
- 12.Split Array Largest Sum (LeetCode 410)
- 13.Median of Two Sorted Arrays (LeetCode 4)