LeetCode #367 Easy

Valid Perfect Square

Valid Perfect Square: decide whether a positive integer num is the square of an integer, without using any built-in square-root function.

Constraints
  • 1 <= num <= 2³¹ - 1
  • You must not use any built-in library function such as sqrt
mathbinary search
Open on LeetCode ↗
Valid Perfect Square diagramA labelled diagram of the structure this problem turns on.no array exists — the squares themselves are the sorted space1² = 1root 12² = 4root 23² = 9root 34² = 16root 45² = 25root 5squaring is increasing, so a probe is decisive:mid² < num ⟹ search right, mid² > num ⟹ search leftinteger products only — a float sqrt can round below the true root
02

Intuition

Valid perfect square tests whether a number is a perfect square, without using any built-in square-root function. That restriction is the point — it forces an actual search or an arithmetic identity. The binary search approach is the general one. Squares are monotonic, so: - Search candidate roots from 1 to n / 2, comparing mid × mid against the target — the predicate is monotonic, which is exactly what binary search needs. The upper bound can be n / 2 for n > 4, since no larger value can square to n, but simply using n is safer and costs one extra iteration. Overflow is the real hazard: mid × mid exceeds 32-bit range for large inputs. Use 64-bit arithmetic, or compare with mid <= n / mid to avoid multiplying at all. Newton's method converges faster, iterating x = (x + n / x) / 2 until it stabilises, then checking whether the result squares to n. It reaches the answer in far fewer steps but needs care with integer division. There is also a neat arithmetic identity worth knowing: Every perfect square is a sum of consecutive odd numbers — 1, 1+3, 1+3+5 give 1, 4, 9. So repeatedly subtracting successive odd numbers and checking for zero decides the question in O(√n) with only subtraction. That is slower than binary search but requires no multiplication at all, which occasionally matters. The boundary cases are 0 and 1, both perfect squares, and both easy to mishandle when the search range starts at 1. Binary search gives O(log n) time and O(1) space.

How to spot this pattern

Binary search on the answer applies whenever a candidate can be tested for being too small or too large, and that test is monotone. No array is needed — only order and a decisive comparison. Sqrt(x), Koko Eating Bananas, and Split Array Largest Sum are the same technique.

03

Approach

Try it first

Before reading on: state why binary search is valid when there is no array to search. Then work out where mid * mid can overflow in a fixed-width language, and how to compare without ever forming the product.

1

Respect the restriction

No built-in square root is allowed, which forces an actual search or an arithmetic identity rather than a library call.

2

Binary search the root

Squares are monotonic, so search candidate roots comparing mid * mid against n. The monotonic predicate is what makes this valid.

3

Guard against overflow

mid * mid exceeds 32-bit range for large inputs. Use 64-bit arithmetic, or compare mid <= n / mid to avoid multiplying entirely.

4

Handle zero and one

Both are perfect squares and both are easy to mishandle when the search range starts at 1. Check them before searching.

5

Know Newton's method

Iterate x = (x + n / x) / 2 until stable, then verify. Converges in far fewer steps, though integer division needs care.

6

Know the odd-number identity

Every perfect square is a sum of consecutive odd numbers — 1, 1+3, 1+3+5. Subtracting them until zero or negative decides it in O(√n) with no multiplication.

7

Cost of the approaches

Binary search is O(log n) time and O(1) space; the odd-number method is O(√n) but avoids multiplication entirely.

04

Solution & live demo

▶1class Solution:
▶2 def isPerfectSquare(self, num):
▶3 lo, hi = 1, num
▶4 while lo <= hi:
▶5 mid = lo + (hi - lo) // 2
▶6 square = mid * mid
▶7 if square == num:
▶8 return True
▶9 if square < num:
▶10 lo = mid + 1
▶11 else:
▶12 hi = mid - 1
▶13 return False
05

Common pitfalls

Overflowing the product in C++ or Java

✗ Wrong
int square = mid * mid;
✓ Right
long square = (long) mid * mid;

With num up to 2³¹ - 1, mid can approach 46341 and mid * mid exceeds the signed 32-bit range, wrapping to a negative value that compares wrongly and sends the search the wrong way.

Trusting a floating-point square root

✗ Wrong
r = int(sqrt(num))
return r * r == num
✓ Right
binary search on integer products

Beyond the range where doubles represent integers exactly, sqrt can return a value a fraction below the true root, so truncation gives r - 1 and a genuine perfect square is reported as false. It is also explicitly disallowed.

Starting the range at 0

✗ Wrong
lo, hi = 0, num
✓ Right
lo, hi = 1, num

Zero is not in the valid input range and only adds a wasted iteration whose product is 0. Worse, in variants that divide by mid it introduces a division-by-zero on the very first probe.

06

Edge cases

num = 1

The range [1,1] gives mid = 1 and 1 * 1 == 1, so true.

num = 2

The interval empties between 1 and 2 without a match, so false.

A large perfect square such as 808201

Found in about twenty iterations; the product must not overflow.

One less than a perfect square, e.g. 15

Falls strictly between 3² and 4², so the range empties and returns false.

num = 4

The first midpoint may overshoot, and hi drops until mid = 2 matches.

07

Complexity

Time
O(log num)
Space
O(1)
The range halves each iteration, so about 31 probes suffice at the maximum input.