A clear explanation of checking whether an integer is a perfect square using binary search without sqrt.
Problem Restatement
Given a positive integer num, return True if it is a perfect square.
A perfect square is an integer that can be written as:
x * xwhere x is also an integer.
We must not use built-in square root functions such as sqrt. The constraint is 1 <= num <= 2^31 - 1.
Input and Output
| Item | Meaning |
|---|---|
| Input | Positive integer num |
| Output | True if num is a perfect square, otherwise False |
| Restriction | Do not use built-in square root functions |
Example function shape:
def isPerfectSquare(num: int) -> bool:
...Examples
Example 1:
num = 16Since:
4 * 4 = 16the answer is:
TrueExample 2:
num = 14There is no integer x such that:
x * x == 14because:
3 * 3 = 9
4 * 4 = 16So the answer is:
FalseFirst Thought: Linear Search
The simplest method is to try every integer from 1 upward.
class Solution:
def isPerfectSquare(self, num: int) -> bool:
x = 1
while x * x <= num:
if x * x == num:
return True
x += 1
return FalseThis works, but it may check many numbers.
For large num, the loop may run around:
sqrt(num)times.
We can do better with binary search.
Key Insight
If x * x is too small, then every smaller number also has a square that is too small.
If x * x is too large, then every larger number also has a square that is too large.
So the predicate has an ordered structure.
That means we can binary search over possible values of x.
The square root of num must be between:
1and:
numinclusive.
Algorithm
Use binary search on the integer range [1, num].
At each step:
- Compute the middle value.
- Compute
middle * middle. - If it equals
num, returnTrue. - If it is smaller than
num, search the right half. - If it is larger than
num, search the left half. - If the search ends without equality, return
False.
Correctness
The search range always contains every possible integer square root that has not been ruled out.
When mid * mid < num, mid and every smaller value cannot be the square root, so the algorithm safely moves left to mid + 1.
When mid * mid > num, mid and every larger value cannot be the square root, so the algorithm safely moves right to mid - 1.
When mid * mid == num, an integer square root has been found, so num is a perfect square.
If the loop ends, every possible integer candidate has been ruled out. Therefore, num is not a perfect square.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(log num) | Binary search halves the range each step |
| Space | O(1) | Only integer variables are stored |
Implementation
class Solution:
def isPerfectSquare(self, num: int) -> bool:
left = 1
right = num
while left <= right:
mid = left + (right - left) // 2
square = mid * mid
if square == num:
return True
if square < num:
left = mid + 1
else:
right = mid - 1
return FalseCode Explanation
The search begins with all possible roots:
left = 1
right = numThe midpoint is computed this way:
mid = left + (right - left) // 2In Python, overflow is not a problem. In languages with fixed-width integers, this form is safer than (left + right) // 2.
Then we compute:
square = mid * midIf it matches num, we found the integer square root.
If the square is too small:
left = mid + 1If the square is too large:
right = mid - 1If no exact square is found, return:
FalseTesting
def run_tests():
s = Solution()
assert s.isPerfectSquare(1) is True
assert s.isPerfectSquare(4) is True
assert s.isPerfectSquare(9) is True
assert s.isPerfectSquare(14) is False
assert s.isPerfectSquare(16) is True
assert s.isPerfectSquare(808201) is True
assert s.isPerfectSquare(2147483647) is False
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
1 | Smallest valid input |
4, 9, 16 | Basic perfect squares |
14 | Between two perfect squares |
808201 | Larger perfect square |
2147483647 | Upper constraint area |