LeetCode 479 - Largest Palindrome Product
The problem asks us to find the largest palindrome number that can be written as the product of two n-digit integers. A palindrome is a number that reads the same forward and backward. For example, 9009 is a palindrome because reversing its digits still gives 9009.
Difficulty: 🔴 Hard
Topics: Math, Enumeration
Solution
Largest Palindrome Product
Problem Understanding
The problem asks us to find the largest palindrome number that can be written as the product of two n-digit integers.
A palindrome is a number that reads the same forward and backward. For example, 9009 is a palindrome because reversing its digits still gives 9009.
The input is a single integer n, where n represents the number of digits in each multiplicand. For example:
- If
n = 2, the valid numbers range from10to99 - If
n = 3, the valid numbers range from100to999
We must search for the largest palindrome that can be expressed as:
$$a \times b$$
where both a and b are n-digit integers.
The problem does not ask us to return the palindrome itself. Instead, we return:
$$\text{palindrome} \bmod 1337$$
This modulus requirement exists because the palindrome can become extremely large for larger values of n.
The constraint is:
1 <= n <= 8
This upper bound is very important. A naive brute force solution would require checking products of numbers as large as 99,999,999, which is computationally infeasible.
An important observation is that the search space grows quadratically:
$$(10^n)^2$$
For n = 8, this becomes enormous, so we need a smarter strategy.
There are several important edge cases:
n = 1is special because the largest palindrome is simply9- Very large palindromes may exceed 32-bit integer limits, so languages like Go must use 64-bit integers
- A naive palindrome check repeated millions of times becomes prohibitively expensive
- The largest palindrome is not necessarily the square of the largest
n-digit number
The problem guarantees that n is always valid and within the allowed range.
Approaches
Brute Force Approach
The most direct solution is to generate every possible product of two n-digit numbers and check whether the product is a palindrome.
For example, when n = 2, we would iterate:
99 * 9999 * 9899 * 97- and so on
For each product:
- Convert the number to a string
- Reverse the string
- Compare with the original
- Track the maximum palindrome found
This approach is correct because it examines every possible pair of n-digit numbers. Eventually, it will encounter the largest valid palindrome.
However, the time complexity is far too large.
For n = 8, there are approximately:
$$(9 \times 10^7)^2$$
pairs to examine, which is completely impractical.
The brute force method also wastes substantial work checking non-palindromic numbers.
Optimal Approach
The key insight is that instead of generating products and checking whether they are palindromes, we can generate palindromes directly.
This drastically reduces the search space.
Suppose we construct palindromes by taking a number and appending its reverse:
91 -> 9119900 -> 900009
If we generate palindromes from largest to smallest, then the first palindrome that can be factored into two n-digit numbers must be the answer.
The second major insight is factor checking.
If a palindrome p can be written as:
$$p = a \times b$$
then one factor must be at least:
$$\sqrt{p}$$
So we only need to search divisors downward from the largest n-digit number.
This turns an impossible brute force search into a manageable enumeration problem.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(10^(2n) * n) | O(1) | Checks every product and tests palindromes |
| Optimal | Roughly O(10^n * 10^(n/2)) in practice | O(1) | Generates palindromes directly and searches factors efficiently |
Algorithm Walkthrough
Step 1: Handle the Special Case
If n == 1, return 9.
The largest product of two 1-digit numbers is:
$$9 \times 9 = 81$$
The largest palindromic product is:
$$9 = 9 \times 1$$
and:
$$9 \bmod 1337 = 9$$
Step 2: Determine the Largest and Smallest n-Digit Numbers
Compute:
upper = 10^n - 1lower = 10^(n - 1)
For n = 2:
upper = 99lower = 10
These bounds define the valid factor range.
Step 3: Generate Palindromes from Largest to Smallest
Start from left = upper.
Construct a palindrome by:
- Converting
leftto a string - Reversing it
- Appending the reverse
For example:
left = 99- reverse =
99 - palindrome =
9999
Then:
left = 98- palindrome =
9889
This guarantees descending palindrome order.
Step 4: Search for Valid Factors
For each palindrome:
- Start divisor
d = upper - While
d * d >= palindrome - Check whether
palindrome % d == 0
If divisible:
$$other = palindrome / d$$
Then verify:
other >= lowerother <= upper
If true, both factors are valid n-digit numbers.
Step 5: Return the Modulo
Once the first valid palindrome is found:
$$return palindrome \bmod 1337$$
Because palindromes are generated in descending order, the first valid one is guaranteed to be the largest.
Why it works
The algorithm generates palindromes from largest to smallest. For each palindrome, it checks whether the palindrome has two valid n-digit factors.
Since every larger palindrome is checked before smaller ones, the first palindrome that satisfies the factorization condition must be the maximum valid answer.
The divisor search is correct because any factor pair must contain a factor at least as large as the square root of the palindrome. Therefore, checking divisors down to that boundary guarantees we do not miss any valid factorization.
Python Solution
class Solution:
def largestPalindrome(self, n: int) -> int:
if n == 1:
return 9
upper = 10 ** n - 1
lower = 10 ** (n - 1)
for left in range(upper, lower - 1, -1):
palindrome = int(str(left) + str(left)[::-1])
divisor = upper
while divisor * divisor >= palindrome:
if palindrome % divisor == 0:
other = palindrome // divisor
if lower <= other <= upper:
return palindrome % 1337
divisor -= 1
return -1
The implementation begins with the special case for n == 1, because this case has a known direct answer.
Next, the code computes the upper and lower bounds for valid n-digit numbers.
The outer loop generates palindromes in descending order. Instead of testing every product, the code constructs candidate palindromes directly by mirroring the left half.
For each palindrome, the inner loop searches for a valid divisor. The condition:
divisor * divisor >= palindrome
ensures we only search until the square root boundary.
When divisibility is found, the paired factor is computed with integer division. If both factors are within the valid range, the palindrome satisfies the problem requirements, and we immediately return the modulo result.
The algorithm terminates early because the palindromes are generated from largest to smallest.
Go Solution
package main
import (
"strconv"
)
func largestPalindrome(n int) int {
if n == 1 {
return 9
}
upper := intPow(10, n) - 1
lower := intPow(10, n-1)
for left := upper; left >= lower; left-- {
palindrome := buildPalindrome(left)
for divisor := upper; divisor*divisor >= palindrome; divisor-- {
if palindrome%divisor == 0 {
other := palindrome / divisor
if other >= lower && other <= upper {
return palindrome % 1337
}
}
}
}
return -1
}
func buildPalindrome(x int) int {
s := strconv.Itoa(x)
reversed := reverseString(s)
value, _ := strconv.Atoi(s + reversed)
return value
}
func reverseString(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
func intPow(base int, exp int) int {
result := 1
for exp > 0 {
result *= base
exp--
}
return result
}
The Go implementation follows the same logic as the Python version.
One important difference is integer handling. Go uses fixed-width integers, so using int is safe here because the maximum palindrome for n <= 8 still fits within 64-bit integer ranges on modern systems.
Unlike Python, Go does not have built-in string reversal utilities, so a helper function is used to reverse the string representation when constructing palindromes.
Go also does not provide a built-in integer exponentiation operator, so a small helper function computes powers of ten.
Worked Examples
Example 1
Input:
n = 2
We compute:
| Variable | Value |
|---|---|
| upper | 99 |
| lower | 10 |
Generate palindromes:
| left | palindrome |
|---|---|
| 99 | 9999 |
| 98 | 9889 |
| 97 | 9779 |
| ... | ... |
| 90 | 9009 |
Now check 9009.
| divisor | 9009 % divisor | valid? |
|---|---|---|
| 99 | 0 | yes |
Compute:
$$9009 / 99 = 91$$
Both 99 and 91 are 2-digit numbers.
Therefore:
$$9009 \bmod 1337 = 987$$
Return:
987
Example 2
Input:
n = 1
Special case immediately returns:
9
because the largest palindromic product is 9.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | Roughly O(10^n * 10^(n/2)) in practice | Generates palindromes and checks divisors efficiently |
| Space | O(1) | Only a few integer variables are used |
The exact runtime is difficult to express tightly because the algorithm terminates early once the largest valid palindrome is found.
The important improvement over brute force is that we avoid enumerating all products. Instead, we enumerate palindrome candidates directly, which dramatically reduces unnecessary work.
The algorithm uses constant extra memory because it stores only a few variables regardless of input size.
Test Cases
solution = Solution()
assert solution.largestPalindrome(1) == 9 # smallest valid n
assert solution.largestPalindrome(2) == 987 # provided example
assert solution.largestPalindrome(3) == 123 # known LeetCode result
assert solution.largestPalindrome(4) == 597 # larger palindrome search
assert solution.largestPalindrome(5) == 677 # stress medium-sized input
assert solution.largestPalindrome(6) == 1218 # larger search space
assert solution.largestPalindrome(7) == 877 # near upper constraints
assert solution.largestPalindrome(8) == 475 # maximum allowed n
Test Summary
| Test | Why |
|---|---|
n = 1 |
Validates special-case handling |
n = 2 |
Confirms provided example |
n = 3 |
Tests standard larger search |
n = 4 |
Verifies correctness on bigger palindromes |
n = 5 |
Exercises more extensive divisor checking |
n = 6 |
Tests performance on larger inputs |
n = 7 |
Ensures scaling remains correct |
n = 8 |
Validates maximum constraint |
Edge Cases
Edge Case 1: n = 1
This is the most important special case. The palindrome generation strategy used for larger values creates even-length palindromes only, such as 9009 or 9889.
For n = 1, the correct answer is the single-digit palindrome 9.
Without explicitly handling this case, the algorithm would fail to return the correct result.
The implementation solves this immediately with:
if n == 1:
return 9
Edge Case 2: Very Large Products
For n = 8, the palindrome can become very large. A naive implementation using 32-bit integers may overflow.
The Python version handles this naturally because Python integers have arbitrary precision.
The Go solution relies on modern 64-bit integer support, which safely accommodates the required values.
Edge Case 3: No Early Divisor Pruning
A naive divisor search might check all possible divisors down to 1.
This is unnecessary because once:
$$d^2 < palindrome$$
all remaining factor pairs would already have been examined in reverse order.
The implementation avoids redundant work with:
while divisor * divisor >= palindrome:
This optimization significantly improves performance and is essential for larger values of n.