278. First Bad Version
On LeetCode ->Reformulated question¶
Find the smallest version in 1..n such that isBadVersion(v) is True, knowing:
- all versions before it are good
- it and all later versions are bad
- you should use as few API calls as possible
Compact example:
n=5, state=[good, good, good, bad, bad]->4
Key trick¶
Use binary search on the boundary between:
- good prefix
- bad suffix
Keep the first bad candidate and move left when mid is bad.
Trap¶
Common mistakes:
- using linear search, which wastes API calls
- off-by-one errors with
left,right, and loop condition - forgetting to return the left boundary directly
- initializing an answer variable unnecessarily
- not handling
n=1
Why this question is interesting¶
It is a clean "find first true" binary search.
It tests whether you really understand boundary-search, not just ordinary value lookup.
Solve the problem with idiomatic python¶
class Solution:
def firstBadVersion(self, n: int) -> int:
left, right = 1, n
# Invariant:
# - first bad version is always inside [left, right]
while left < right:
mid = left + (right - left) // 2
if isBadVersion(mid):
# mid can still be the first bad version
right = mid
else:
# first bad version must be after mid
left = mid + 1
return left
Pytest test¶
import pytest
@pytest.mark.parametrize(
("n", "bad", "expected"),
[
(1, 1, 1),
(2, 1, 1),
(2, 2, 2),
(5, 4, 4),
(5, 1, 1),
(5, 5, 5),
(8, 3, 3),
(10, 7, 7),
],
)
def test_first_bad_version(n, bad, expected, monkeypatch):
import leetcode_top_easy_05_sorting_and_searching_02_first_bad_version as mod
def fake_isBadVersion(version):
return version >= bad
monkeypatch.setattr(mod, "isBadVersion", fake_isBadVersion)
assert mod.Solution().firstBadVersion(n) == expected
Comment my solution¶
Good:
- correct binary search idea
- updates bounds correctly
- works on the provided cases
Can be improved:
first_bad_vis not neededwhile (max_v - min_v) >= 0:is harder to read thanwhile left < right- names like
min_vandmax_vare less standard thanleftandright
def isBadVersion(version):
pass
class Solution:
def firstBadVersion(self, n: int) -> int:
first_bad_v = 1
min_v = 1
max_v = n
while (max_v - min_v) >= 0:
v = min_v + (max_v - min_v) // 2
if isBadVersion(v):
first_bad_v = v
max_v = v - 1
else:
min_v = v + 1
return first_bad_v
import pytest
import leetcode_top_easy_05_sorting_and_searching_02_first_bad_version as mod
@pytest.mark.parametrize(
("version_fails", "first_bad_v"),
[
([True], 1),
([False, False, True, True, True], 3),
([False, True, True, True, True], 2),
([False, True, True, True], 2),
([False, False, True], 3),
]
)
def test_firstBadVersion(version_fails, first_bad_v, monkeypatch):
def fake_isBadVersion(version):
return version_fails[version - 1]
monkeypatch.setattr(mod, "isBadVersion", fake_isBadVersion)
assert Solution().firstBadVersion(len(version_fails)) == first_bad_v
A simpler version is:
class Solution:
def firstBadVersion(self, n: int) -> int:
left, right = 1, n
while left < right:
mid = left + (right - left) // 2
if isBadVersion(mid):
right = mid
else:
left = mid + 1
return left
Code¶
import pytest
# The API is provided by the platform.
def isBadVersion(version: int) -> bool:
raise NotImplementedError
class Solution:
def firstBadVersion(self, n: int) -> int:
left, right = 1, n
# Binary search for the first version where isBadVersion(version) is True.
while left < right:
mid = left + (right - left) // 2
if isBadVersion(mid):
# mid may be the first bad version, keep it.
right = mid
else:
# mid is good, so first bad is strictly to the right.
left = mid + 1
return left
@pytest.mark.parametrize(
("n", "bad", "expected"),
[
(1, 1, 1),
(2, 1, 1),
(2, 2, 2),
(5, 4, 4),
(5, 1, 1),
(5, 5, 5),
(8, 3, 3),
(10, 7, 7),
],
)
def test_first_bad_version(n, bad, expected, monkeypatch):
import leetcode_top_easy_05_sorting_and_searching_02_first_bad_version as mod
def fake_isBadVersion(version):
return version >= bad
monkeypatch.setattr(mod, "isBadVersion", fake_isBadVersion)
assert mod.Solution().firstBadVersion(n) == expected
# Interactive examples:
#
# n=5, bad=4
# states: [False, False, False, True, True]
# answer: 4
#
# n=1, bad=1
# states: [True]
# answer: 1