Sqrt(x): Binary Search for the Integer Square Root
Sqrt(x) is LeetCode 69, and it is one of the cleanest examples of binary search on the answer space. You are not searching through an array. You are searching through all candidate integers to find the largest one whose square is at most x. If you have seen the binary search pattern, this problem is a direct application of the search-for-boundary variant.
The problem
Given a non-negative integer x, return the square root of x rounded down to the nearest integer. You must not use any built-in exponent function or operator.
x = 4returns2, becausesqrt(4) = 2exactly.x = 8returns2, becausesqrt(8) = 2.828...and you round down.
The answer space is the range of integers from 0 to x. For each candidate mid, you check whether mid * mid is at most x. The candidates that pass form a contiguous block on the left, and the ones that fail form a contiguous block on the right. You want the rightmost candidate that passes.
The approach: binary search on the answer space
The key observation is that the condition mid * mid is at most x is monotonic over the range [0, x]. Every candidate from 0 up to some value passes, and every candidate after that fails. That monotonic boundary is exactly what binary search finds efficiently.
The plan:
- Set
lo = 0andhi = x. - Compute
mid = lo + (hi - lo) // 2. - If
mid * midis at mostx, thenmidmight be the answer or something larger might work too. Setlo = mid + 1. - If
mid * midis greater thanx, thenmidis too large. Sethi = mid - 1. - When
lo > hi, returnhi. It holds the largest value whose square fits.
This uses the lo is at most hi form of binary search (with hi = mid - 1 and lo = mid + 1). When the loop exits, hi points to the last valid candidate. This is the search-for-boundary pattern applied to finding a rightmost valid value.
Visual walkthrough: x = 8
The search space is [0, 8]. You are looking for the largest integer whose square is at most 8.
Step 1: lo=0, hi=8, mid=4. 4*4=16. 16 > 8, so 4 is too large.
mid*mid exceeds x. The answer must be smaller. Set hi = mid - 1 = 3.
Step 2: lo=0, hi=3, mid=1. 1*1=1. 1 is at most 8, so 1 fits.
mid*mid fits within x. Maybe a larger value also fits. Set lo = mid + 1 = 2.
Step 3: lo=2, hi=3, mid=2. 2*2=4. 4 is at most 8, so 2 fits.
mid*mid fits within x. Try larger. Set lo = mid + 1 = 3.
Step 4: lo=3, hi=3, mid=3. 3*3=9. 9 > 8, so 3 is too large.
mid*mid exceeds x. Set hi = mid - 1 = 2. Now lo > hi, so the loop exits.
Result: lo=3, hi=2. lo > hi. Return hi = 2.
The search is complete. The integer square root of 8 is 2, since 2*2=4 fits but 3*3=9 does not.
Four iterations to find the answer, even though the search space had 9 candidates. With larger values of x, the savings are dramatic. For x = 2,147,483,647 (the 32-bit integer max), binary search takes about 31 steps instead of two billion.
The code
Here is the complete Python solution for LeetCode 69:
def mySqrt(x):
lo, hi = 0, x
while lo <= hi:
mid = lo + (hi - lo) // 2
if mid * mid <= x:
lo = mid + 1
else:
hi = mid - 1
return hi
lo = 0, hi = x. The smallest possible square root is 0 (when x = 0). The largest possible is x itself (since for x of at least 1, sqrt(x) is at most x).
while lo is at most hi. You keep searching until the pointers cross. This is the standard form for finding the rightmost valid value.
if mid * mid is at most x: lo = mid + 1. The current mid fits, but a larger value might also fit. Move lo past mid to search right.
else: hi = mid - 1. The square of mid exceeds x. This candidate and everything larger is invalid. Move hi below mid.
Return hi. When the loop exits, lo has passed hi. The value hi is the last candidate where mid * mid was at most x.
Complexity analysis
Time: O(log x). Each iteration halves the search space. For x up to 2^31 - 1, that is at most 31 iterations.
Space: O(1). Just three variables: lo, hi, and mid.
Edge cases to watch for
x = 0: The answer is 0. The loop body never executes becauselo = 0andhi = 0, somid = 0, and0 * 0 = 0is at most 0. Thenlobecomes 1,lo > hi, and you returnhi = 0.x = 1:mid = 0first,0 * 0 = 0fits,lo = 1. Thenmid = 1,1 * 1 = 1fits,lo = 2. Nowlo > hi, returnhi = 1.- Perfect square (e.g.,
x = 16): Whenmid = 4,4 * 4 = 16is at most 16, solomoves to 5. The search continues, but 5 and above all fail.hiends at 4. Correct. - Large
xand overflow: In Python, integers have arbitrary precision, somid * midnever overflows. In languages like Java or C++, you should comparemidagainstx // midinstead of computingmid * midto avoid integer overflow.
The building blocks
This problem is built from two reusable building blocks.
Binary search on the answer space. Instead of searching a sorted array, you search a range of candidate answers [0, x]. The condition mid * mid is at most x plays the role of the comparison. This is the same structural pattern used in Koko Eating Bananas, Split Array Largest Sum, and Capacity to Ship Packages.
Search-for-boundary pattern. You are not looking for an exact match. You are looking for the boundary between values that satisfy a condition and values that do not. Specifically, you want the rightmost value where the condition holds. The lo = mid + 1 / hi = mid - 1 template with a return of hi is the standard way to find this rightmost boundary.
# Rightmost boundary: find the largest mid where condition(mid) is true
lo, hi = lower_bound, upper_bound
while lo <= hi:
mid = lo + (hi - lo) // 2
if condition(mid):
lo = mid + 1
else:
hi = mid - 1
return hi
For Sqrt(x), condition(mid) is mid * mid is at most x. Plug it in and the problem is solved.
From understanding to recall
The logic here is simple: binary search a range, compare the square of the midpoint to x, narrow down. You probably see exactly how it works right now.
But picture yourself writing this in an interview in three weeks. Is it lo = mid + 1 or lo = mid? Do you return lo or hi? What are the initial bounds for hi? These details are small but they matter, and they are easy to mix up under pressure.
Spaced repetition fixes this. You type the solution from scratch today, again tomorrow, then in three days. After a few rounds, the boundary template and the return value are automatic. You do not rederive anything. You just write it.
Sqrt(x) is a perfect SRS candidate: the code is about 8 lines, the pattern transfers to harder problems, and the off-by-one decisions are exactly the kind of thing that trips people up when they have not drilled them.
Related posts
- Binary Search: More Than Finding a Number - The full binary search pattern guide, including the search-on-answer and search-for-boundary variants that Sqrt(x) is built on
- Koko Eating Bananas: Binary Search on the Answer - Another binary search on answer problem, this time finding the minimum valid speed rather than the maximum valid square root