A clear explanation of counting super-palindromes by generating palindromic roots and checking their squares.
Problem Restatement
A positive integer is called a super-palindrome if:
- The number itself is a palindrome.
- It is the square of another palindrome.
We are given two positive integers left and right as strings.
Return how many super-palindromes are in the inclusive range:
[left, right]For example, 9 is a super-palindrome because:
9 = 3 * 3and both 9 and 3 are palindromes.
The official problem uses string inputs because the range can go up to around 10^18. The definition and examples match the LeetCode statement.
Input and Output
| Item | Meaning |
|---|---|
| Input | Strings left and right |
| Output | Number of super-palindromes in the inclusive range |
| Rule 1 | The number must be a palindrome |
| Rule 2 | Its square root must also be a palindrome integer |
| Range | left <= x <= right |
Function shape:
def superpalindromesInRange(left: str, right: str) -> int:
...Examples
Example 1:
left = "4"
right = "1000"The super-palindromes are:
4, 9, 121, 484They come from palindromic roots:
2 * 2 = 4
3 * 3 = 9
11 * 11 = 121
22 * 22 = 484Answer:
4676 is a palindrome, but it is not counted because:
676 = 26 * 26and 26 is not a palindrome.
Example 2:
left = "1"
right = "2"The only valid number is:
1Answer:
1First Thought: Check Every Number
A direct approach is to loop through every number from left to right.
For each number x:
- Check whether
xis a palindrome. - Check whether
xhas an integer square root. - Check whether that square root is also a palindrome.
This is far too slow because right can be as large as 10^18 - 1 under the constraints listed by common references for the problem.
We need to search through possible roots instead.
Key Insight
If x is a super-palindrome, then:
x = p * pwhere p is a palindrome.
So instead of checking every possible x, we generate palindromic roots p, square them, and check whether the square is also a palindrome.
Since:
x <= 10^18 - 1we only need:
p <= sqrt(10^18 - 1)That is less than:
10^9But we still should not loop over all numbers up to 10^9.
Instead, we generate only palindromic roots.
A palindrome root can be generated from its first half.
For example, from "123":
Odd length palindrome:
"123" + "21" = "12321"Even length palindrome:
"123" + "321" = "123321"This gives all palindromic roots without testing every integer.
Algorithm
Convert bounds to integers:
L = int(left)
R = int(right)Then enumerate possible first halves.
For each integer half:
- Convert
halfto string. - Build an odd-length palindrome root.
- Square it.
- If the square is in range and is a palindrome, count it.
- Build an even-length palindrome root.
- Square it.
- If the square is in range and is a palindrome, count it.
We can stop once the smallest generated square is larger than R, but using a safe fixed bound also works.
Since roots are below 10^9, their first half has at most 5 digits. So enumerating half from 1 to 100000 is enough.
Palindrome Check
We can check palindromes by converting the number to a string:
def is_palindrome(x: int) -> bool:
s = str(x)
return s == s[::-1]This is simple and fast enough here.
Correctness
Every number counted by the algorithm is valid.
The algorithm only counts a square square = root * root when:
rootwas generated as a palindrome.squarelies in[L, R].squareis also a palindrome.
Therefore, every counted number is a super-palindrome in the required range.
Now we show that every valid super-palindrome is counted.
Let x be any super-palindrome in [L, R].
By definition, there exists a palindromic integer p such that:
x = p * pSince x <= R <= 10^18 - 1, we have p < 10^9.
Every positive palindrome less than 10^9 can be formed by taking its first half and mirroring it, either as an odd-length or even-length palindrome.
The algorithm enumerates all such first halves and constructs both odd and even palindromic roots.
So it eventually constructs p, computes p * p, checks that it lies in range, and counts it.
Thus no valid super-palindrome is missed.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(100000 * d) | We generate palindromic roots from up to 100000 halves and do digit checks |
| Space | O(1) | We only store counters and temporary strings |
Here d is the number of digits, at most 18 for the squared value.
Implementation
class Solution:
def superpalindromesInRange(self, left: str, right: str) -> int:
L = int(left)
R = int(right)
def is_palindrome(x: int) -> bool:
s = str(x)
return s == s[::-1]
answer = 0
for half in range(1, 100000):
s = str(half)
odd_root = int(s + s[-2::-1])
odd_square = odd_root * odd_root
if odd_square > R:
break
if odd_square >= L and is_palindrome(odd_square):
answer += 1
even_root = int(s + s[::-1])
even_square = even_root * even_root
if even_square >= L and even_square <= R and is_palindrome(even_square):
answer += 1
return answerCode Explanation
The bounds arrive as strings, so we convert them first:
L = int(left)
R = int(right)The helper checks whether a number reads the same backward and forward:
def is_palindrome(x: int) -> bool:
s = str(x)
return s == s[::-1]We enumerate possible left halves:
for half in range(1, 100000):For odd-length roots, we do not duplicate the middle digit:
odd_root = int(s + s[-2::-1])For example:
s = "123"
odd_root = 12321Then we square the root:
odd_square = odd_root * odd_rootIf the odd square already exceeds the right bound, later odd roots will only be larger, so we can stop:
if odd_square > R:
breakThen we count it if it is inside the range and palindromic:
if odd_square >= L and is_palindrome(odd_square):
answer += 1For even-length roots, we mirror the whole half:
even_root = int(s + s[::-1])For example:
s = "123"
even_root = 123321Then we check its square in the same way:
if even_square >= L and even_square <= R and is_palindrome(even_square):
answer += 1Testing
def run_tests():
s = Solution()
assert s.superpalindromesInRange("4", "1000") == 4
assert s.superpalindromesInRange("1", "2") == 1
assert s.superpalindromesInRange("1", "1") == 1
assert s.superpalindromesInRange("2", "3") == 0
assert s.superpalindromesInRange("9", "9") == 1
assert s.superpalindromesInRange("121", "121") == 1
assert s.superpalindromesInRange("122", "483") == 0
assert s.superpalindromesInRange("484", "484") == 1
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
"4" to "1000" | Main sample range |
"1" to "2" | Small range with one valid value |
"1" to "1" | Single-value inclusive bound |
"2" to "3" | No valid super-palindrome |
"9" to "9" | Single-value square of palindromic root |
"121" to "121" | 11 * 11 |
"122" to "483" | Gap between known values |
"484" to "484" | 22 * 22 |