Square Root (Integer) asks for the whole-number part of a square root without ever calling sqrt. It is a clean, classic use of binary search on the answer — the search space is not an array you are given, but the range of possible answers itself.
Problem. Given a non-negative integer x, return floor(sqrt(x)) — the largest integer m such
that m * m <= x. You may not use any built-in square-root function.
Example: x = 50 → answer 7 (because 7 * 7 = 49 <= 50, but 8 * 8 = 64 > 50).
The slow way first
The obvious idea: count up from 0, squaring each candidate, until the square passes x, then back off by one. For x = 50 you would test 0, 1, 2, ... , 7, 8 and stop. That works, but it is O(sqrt(x)) — for very large x (think billions) you would loop tens of thousands of times.
The question to ask: is the candidate range sorted? It is. If m * m <= x, then every smaller value also squares to something <= x; if m * m > x, every larger value is too big as well. That monotonic split is exactly what binary search needs.
The idea: binary-search the answer
The answer lives somewhere in [0, x]. Keep a window [lo, hi] and repeatedly test its middle:
- Compute
mid = (lo + hi) // 2. - If
mid * mid <= x, thenmidis a valid root — record it as the best answer so far, and look for something even bigger by movinglo = mid + 1. - Otherwise
midis too big, so throw away the upper half:hi = mid - 1.
Each test halves the window, so we finish in O(log x) instead of O(sqrt(x)).
The trick is the record-and-keep-going move: when a candidate fits, we do not stop — we save it and push lo higher, because there might be an even larger root that also fits. The last value we record is the floor.
Walk through it
Step through the animation for x = 50. The strip shows candidate roots 0..10; the lo, mid, and hi pointers bracket the live window and ans tracks the best root found. We test mid = 25 (too big), then 12 (too big), then 5 (fits, ans = 5), then 8 (too big), then 6 (fits, ans = 6), then 7 (fits, ans = 7). Finally lo passes hi, the loop ends, and we return 7.
Pseudocode
lo, hi = 0, x # the answer is somewhere in [0, x]
ans = 0
while lo <= hi:
mid = (lo + hi) // 2
if mid * mid <= x: # mid is a valid root
ans = mid # remember it as the best so far
lo = mid + 1 # try for something bigger
else:
hi = mid - 1 # mid too big, shrink the upper end
return ans # the largest valid root we sawThe Python solution
def my_sqrt(x):
lo, hi, ans = 0, x, 0
while lo <= hi:
mid = (lo + hi) // 2
if mid * mid <= x:
ans = mid
lo = mid + 1
else:
hi = mid - 1
return anslo, hi, ans = 0, x, 0opens the window on the full range and seeds the best answer at0.while lo <= hikeeps going as long as the window is non-empty.mid = (lo + hi) // 2is integer division, somidis always a whole candidate root.- Line 5 is the decision:
mid * mid <= xasks whethermidis a valid root (nosqrt, just one multiply). - Lines 6-7 are the record-and-go-right case — save
mid, then pushloup to hunt for a larger root. hi = mid - 1is the go-left case, dropping the half that is provably too big.- We return
ans, the largest root we ever recorded — the floor of the true square root.
Complexity
| Case | Time | Notes |
|---|---|---|
| Linear scan | O(sqrt x) (moderate) | count up until the square passes x |
| Binary search (this solution) | O(log x) (moderate) | the window halves every round |
O(1) (fast)We use only a handful of integer variables, so the extra space is O(1). Halving the candidate range each step turns an O(sqrt(x)) scan into an O(log x) search — for x near a billion, that is roughly 30 iterations instead of 30,000.
When this pattern shows up
When a problem asks for the largest or smallest value satisfying a monotonic condition (everything below it passes, everything above it fails, or vice versa), you can binary-search the answer even when there is no array. Integer square root, "minimum capacity to ship within D days," and "smallest divisor" are all the same move.
Two pitfalls. First, use mid * mid <= x, never mid <= x / mid with floats — floating point can give
the wrong boundary. Second, remember to record mid before moving lo; if you forget, you return a
stale or zero answer when the loop exits.
Practice
For x = 50 the window is lo = 6, hi = 7. What is mid, what is mid * mid, and which way do we move?
1. Why can we binary-search the candidate roots instead of scanning them?
2. When mid * mid <= x, what do we do?
3. What is the time complexity of the binary-search solution?
4. For x = 50, why is the final answer 7 and not 8?