Facebook Pixel

564. Find the Closest Palindrome

Problem Description

Given a string n that represents an integer, you need to find the closest palindrome number to this integer. The returned palindrome should not be the number itself.

A palindrome is a number that reads the same forwards and backwards (like 121, 1331, or 9).

The "closest" palindrome means the one with the minimum absolute difference from the original number. If there are two palindromes with the same distance from the original number (a tie), you should return the smaller one.

For example:

  • If n = "123", the closest palindrome is "121" (distance of 2)
  • If n = "1", there are two palindromes at the same distance: "0" (distance of 1) and "2" (distance of 1). Since there's a tie, return "0" as it's smaller

The key challenge is handling various edge cases:

  • Numbers like 99, 999 where the closest palindrome might be 101, 1001 (crossing digit boundaries)
  • Numbers that are already palindromes (you need to find the next closest one)
  • Single digit numbers
  • Numbers where multiple palindromes are equidistant from the input

The solution generates candidate palindromes by:

  1. Creating palindromes one digit shorter (10^(l-1) - 1, like 99 for a 3-digit number)
  2. Creating palindromes one digit longer (10^l + 1, like 1001 for a 3-digit number)
  3. Creating palindromes by mirroring the left half with small adjustments (-1, 0, +1 to handle cases where the middle digits need to change)

Then it selects the candidate with minimum distance from the original number, choosing the smaller value in case of ties.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When looking for the nearest palindrome, we need to think about where palindromes can exist relative to our number. The key insight is that we don't need to check every possible number - palindromes have a special structure that limits our search space.

First, consider what makes a palindrome: the left half mirrors to create the right half. So for a number like 12345, potential nearby palindromes could be formed by taking prefixes like 123 and mirroring them to get 12321.

But simply mirroring the left half might give us the original number itself (if it's already a palindrome) or might not give us the closest one. For instance, if we have 12399, mirroring 123 gives us 12321, but 12421 (formed by incrementing the middle and mirroring) might be closer.

This leads us to consider three variations of mirroring:

  • Mirror the left half as-is
  • Decrement the left half by 1, then mirror it
  • Increment the left half by 1, then mirror it

These three candidates handle most cases, but there are special edge cases at digit boundaries. Consider 999 - the nearest palindromes are 1001 (going up) and 989 (going down). The number 1001 represents crossing into 4-digit palindromes, while 989 stays within 3 digits.

This observation reveals that we also need to consider:

  • The largest palindrome with one fewer digit: 10^(l-1) - 1 (like 99 for a 3-digit input)
  • The smallest palindrome with one more digit: 10^l + 1 (like 1001 for a 3-digit input)

By generating these 5 candidates (3 from mirroring variations + 2 from boundary cases), we guarantee that one of them will be the nearest palindrome. We then simply compare distances and pick the closest one, using the smaller value to break ties.

The elegance of this approach is that it reduces an potentially infinite search space to just 5 candidates, leveraging the mathematical structure of palindromes to ensure we don't miss the optimal answer.

Learn more about Math patterns.

Solution Approach

The implementation follows our intuition by systematically generating the candidate palindromes and selecting the best one.

Step 1: Initialize candidate set with boundary palindromes

res = {10 ** (l - 1) - 1, 10**l + 1}

We start by adding the two boundary cases:

  • 10^(l-1) - 1: The largest palindrome with one fewer digit (e.g., 99 for a 3-digit number)
  • 10^l + 1: The smallest palindrome with one more digit (e.g., 1001 for a 3-digit number)

Step 2: Extract the left half of the number

left = int(n[: (l + 1) >> 1])

We take the first (l+1)//2 digits. The bit shift (l + 1) >> 1 is equivalent to (l + 1) // 2. For odd-length numbers, this includes the middle digit; for even-length numbers, it's exactly half.

Step 3: Generate palindromes by mirroring variations

for i in range(left - 1, left + 2):
    j = i if l % 2 == 0 else i // 10
    while j:
        i = i * 10 + j % 10
        j //= 10
    res.add(i)

For each variation (left - 1, left, left + 1):

  • If the original length is even, we mirror the entire left part
  • If the original length is odd, we skip the middle digit by dividing by 10 first (i // 10)
  • The while loop performs the mirroring by extracting digits from j and appending them to i

For example, with 12345 (odd length, left = 123):

  • When i = 123, j = 12 (removing middle), we build 123123212321
  • When i = 122, j = 12, we build 122122212221
  • When i = 124, j = 12, we build 124124212421

Step 4: Remove the original number and find the closest

res.discard(x)  # Remove the original number itself

ans = -1
for t in res:
    if (
        ans == -1
        or abs(t - x) < abs(ans - x)
        or (abs(t - x) == abs(ans - x) and t < ans)
    ):
        ans = t

We iterate through all candidates and select the one that:

  1. Has the minimum absolute difference from the original number
  2. In case of a tie (same distance), choose the smaller number

The use of a set for res automatically handles duplicate candidates, and the final loop ensures we pick the optimal palindrome according to the problem's requirements.

This approach guarantees finding the nearest palindrome in O(1) time (since we only check 5 candidates) with O(1) space complexity.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with n = "123":

Step 1: Initialize boundary candidates

  • Length l = 3
  • Add 10^(3-1) - 1 = 99 (largest 2-digit palindrome)
  • Add 10^3 + 1 = 1001 (smallest 4-digit palindrome)
  • Candidates: {99, 1001}

Step 2: Extract left half

  • Left half length: (3+1)//2 = 2
  • Left = 12 (first 2 digits of "123")

Step 3: Generate mirrored palindromes For each variation of left half:

Variation 1: left - 1 = 11

  • Since length is odd, j = 11 // 10 = 1
  • Build palindrome: 1111 * 10 + (1 % 10) = 111
  • Add 111 to candidates

Variation 2: left = 12

  • Since length is odd, j = 12 // 10 = 1
  • Build palindrome: 1212 * 10 + (1 % 10) = 121
  • Add 121 to candidates

Variation 3: left + 1 = 13

  • Since length is odd, j = 13 // 10 = 1
  • Build palindrome: 1313 * 10 + (1 % 10) = 131
  • Add 131 to candidates

Candidates now: {99, 111, 121, 131, 1001}

Step 4: Remove original and find closest

  • Original number x = 123
  • Remove 123 from candidates (not present, so no change)
  • Calculate distances:
    • |99 - 123| = 24
    • |111 - 123| = 12
    • |121 - 123| = 2 ← minimum distance
    • |131 - 123| = 8
    • |1001 - 123| = 878

Result: "121" (closest palindrome with distance of 2)

Solution Implementation

1class Solution:
2    def nearestPalindromic(self, n: str) -> str:
3        """
4        Find the nearest palindrome to the given number n.
5        If there are two palindromes with the same distance, return the smaller one.
6        """
7        original_num = int(n)
8        length = len(n)
9      
10        # Initialize candidate set with edge cases:
11        # 1. Largest palindrome with (length-1) digits: 999...999
12        # 2. Smallest palindrome with (length+1) digits: 100...001
13        candidates = {10 ** (length - 1) - 1, 10 ** length + 1}
14      
15        # Extract the left half of the number (including middle digit if odd length)
16        prefix_length = (length + 1) // 2
17        prefix = int(n[:prefix_length])
18      
19        # Generate palindromes by mirroring prefix-1, prefix, and prefix+1
20        for prefix_variant in range(prefix - 1, prefix + 2):
21            # Create palindrome by mirroring the prefix
22            palindrome = prefix_variant
23          
24            # Determine what to mirror (exclude middle digit if odd length)
25            suffix_to_mirror = prefix_variant if length % 2 == 0 else prefix_variant // 10
26          
27            # Mirror the digits to create the full palindrome
28            while suffix_to_mirror > 0:
29                palindrome = palindrome * 10 + suffix_to_mirror % 10
30                suffix_to_mirror //= 10
31          
32            candidates.add(palindrome)
33      
34        # Remove the original number from candidates (we need a different palindrome)
35        candidates.discard(original_num)
36      
37        # Find the nearest palindrome from candidates
38        nearest_palindrome = -1
39        for candidate in candidates:
40            distance_to_candidate = abs(candidate - original_num)
41            distance_to_current_nearest = abs(nearest_palindrome - original_num)
42          
43            # Update if this is the first candidate, or if it's closer,
44            # or if it's equally close but smaller
45            if (nearest_palindrome == -1 or 
46                distance_to_candidate < distance_to_current_nearest or
47                (distance_to_candidate == distance_to_current_nearest and 
48                 candidate < nearest_palindrome)):
49                nearest_palindrome = candidate
50      
51        return str(nearest_palindrome)
52
1class Solution {
2    /**
3     * Finds the closest palindrome to the given number string.
4     * If there are two palindromes equally close, returns the smaller one.
5     * 
6     * @param n The input number as a string
7     * @return The nearest palindrome as a string
8     */
9    public String nearestPalindromic(String n) {
10        long originalNumber = Long.parseLong(n);
11        long nearestPalindrome = -1;
12      
13        // Check all candidate palindromes and find the closest one
14        for (long candidate : getCandidatePalindromes(n)) {
15            if (nearestPalindrome == -1 || 
16                Math.abs(candidate - originalNumber) < Math.abs(nearestPalindrome - originalNumber) ||
17                (Math.abs(candidate - originalNumber) == Math.abs(nearestPalindrome - originalNumber) && 
18                 candidate < nearestPalindrome)) {
19                nearestPalindrome = candidate;
20            }
21        }
22      
23        return Long.toString(nearestPalindrome);
24    }
25
26    /**
27     * Generates all possible candidate palindromes for comparison.
28     * Candidates include:
29     * 1. Edge cases: 999...9 and 1000...001 (one digit less/more)
30     * 2. Palindromes formed by mirroring the left half with -1, 0, +1 adjustments
31     * 
32     * @param n The input number as a string
33     * @return Set of candidate palindrome numbers
34     */
35    private Set<Long> getCandidatePalindromes(String n) {
36        int length = n.length();
37        Set<Long> candidates = new HashSet<>();
38      
39        // Add edge case: largest palindrome with one less digit (e.g., 99 for 100)
40        candidates.add((long) Math.pow(10, length - 1) - 1);
41      
42        // Add edge case: smallest palindrome with one more digit (e.g., 1001 for 999)
43        candidates.add((long) Math.pow(10, length) + 1);
44      
45        // Extract the left half of the number (including middle digit if odd length)
46        long leftHalf = Long.parseLong(n.substring(0, (length + 1) / 2));
47      
48        // Generate palindromes by adjusting the left half by -1, 0, and +1
49        for (long adjustment = leftHalf - 1; adjustment <= leftHalf + 1; adjustment++) {
50            StringBuilder palindromeBuilder = new StringBuilder();
51          
52            // Append the adjusted left half
53            palindromeBuilder.append(adjustment);
54          
55            // Mirror the left half to create the right half
56            // If length is odd, skip the middle digit when mirroring
57            String leftHalfString = Long.toString(adjustment);
58            int startIndex = length & 1; // 1 if odd length, 0 if even
59            palindromeBuilder.append(new StringBuilder(leftHalfString).reverse().substring(startIndex));
60          
61            candidates.add(Long.parseLong(palindromeBuilder.toString()));
62        }
63      
64        // Remove the original number itself from candidates
65        candidates.remove(Long.parseLong(n));
66      
67        return candidates;
68    }
69}
70
1class Solution {
2public:
3    string nearestPalindromic(string n) {
4        long originalNum = stol(n);
5        long nearestPalindrome = -1;
6      
7        // Get all palindrome candidates
8        for (long candidate : getPalindromeCandidates(n)) {
9            // Update nearest palindrome based on smallest difference or smaller value when tied
10            if (nearestPalindrome == -1 || 
11                abs(candidate - originalNum) < abs(nearestPalindrome - originalNum) || 
12                (abs(candidate - originalNum) == abs(nearestPalindrome - originalNum) && candidate < nearestPalindrome)) {
13                nearestPalindrome = candidate;
14            }
15        }
16      
17        return to_string(nearestPalindrome);
18    }
19
20private:
21    unordered_set<long> getPalindromeCandidates(string& n) {
22        int length = n.size();
23        unordered_set<long> candidates;
24      
25        // Edge case 1: Palindrome with all 9s (e.g., 999 for length 3)
26        // This handles cases like 100 -> 99
27        candidates.insert((long)pow(10, length - 1) - 1);
28      
29        // Edge case 2: Palindrome with 1...001 pattern (e.g., 1001 for length 4)
30        // This handles cases like 999 -> 1001
31        candidates.insert((long)pow(10, length) + 1);
32      
33        // Get the left half of the number (including middle digit for odd length)
34        long leftHalf = stol(n.substr(0, (length + 1) / 2));
35      
36        // Generate palindromes by mirroring left half, left half - 1, and left half + 1
37        for (long i = leftHalf - 1; i <= leftHalf + 1; ++i) {
38            string prefix = to_string(i);
39          
40            // Create palindrome by appending reversed prefix
41            // For odd length, skip the middle digit when reversing (hence the length & 1)
42            string palindrome = prefix + string(prefix.rbegin() + (length & 1), prefix.rend());
43          
44            candidates.insert(stol(palindrome));
45        }
46      
47        // Remove the original number itself from candidates
48        candidates.erase(stol(n));
49      
50        return candidates;
51    }
52};
53
1/**
2 * Finds the closest palindrome to a given number (excluding the number itself)
3 * @param n - The input number as a string
4 * @returns The nearest palindrome as a string
5 */
6function nearestPalindromic(n: string): string {
7    const originalNumber: bigint = BigInt(n);
8    let closestPalindrome: bigint | null = null;
9
10    // Iterate through all palindrome candidates
11    for (const candidate of getCandidates(n)) {
12        // Update closest palindrome if:
13        // 1. It's the first candidate, or
14        // 2. Current candidate is closer to original number, or
15        // 3. Same distance but current candidate is smaller
16        if (
17            closestPalindrome === null ||
18            absDiff(candidate, originalNumber) < absDiff(closestPalindrome, originalNumber) ||
19            (absDiff(candidate, originalNumber) === absDiff(closestPalindrome, originalNumber) && candidate < closestPalindrome)
20        ) {
21            closestPalindrome = candidate;
22        }
23    }
24
25    return closestPalindrome!.toString();
26}
27
28/**
29 * Generates all possible palindrome candidates for finding the nearest palindrome
30 * @param n - The input number as a string
31 * @returns A Set containing all palindrome candidates
32 */
33function getCandidates(n: string): Set<bigint> {
34    const length: number = n.length;
35    const candidates: Set<bigint> = new Set();
36
37    // Edge case 1: Palindrome with all 9s (e.g., 99 for length 2)
38    candidates.add(BigInt(Math.pow(10, length - 1) - 1));
39  
40    // Edge case 2: Palindrome with 1...1 (e.g., 101 for length 2)
41    candidates.add(BigInt(Math.pow(10, length) + 1));
42
43    // Get the left half (including middle digit for odd length)
44    const leftHalf: bigint = BigInt(n.substring(0, Math.ceil(length / 2)));
45
46    // Generate palindromes by mirroring left half with -1, 0, +1 adjustments
47    for (let i: bigint = leftHalf - 1n; i <= leftHalf + 1n; i++) {
48        const prefix: string = i.toString();
49      
50        // Create palindrome by mirroring prefix
51        // For odd length, skip the middle digit when reversing
52        const palindrome: string = 
53            prefix +
54            prefix
55                .split('')
56                .reverse()
57                .slice(length % 2)
58                .join('');
59              
60        candidates.add(BigInt(palindrome));
61    }
62
63    // Remove the original number from candidates (we need a different palindrome)
64    candidates.delete(BigInt(n));
65  
66    return candidates;
67}
68
69/**
70 * Calculates the absolute difference between two bigint values
71 * @param a - First bigint value
72 * @param b - Second bigint value
73 * @returns The absolute difference as a bigint
74 */
75function absDiff(a: bigint, b: bigint): bigint {
76    return a > b ? a - b : b - a;
77}
78

Time and Space Complexity

Time Complexity: O(n) where n is the length of the input string.

  • Converting string to integer: O(n)
  • Calculating boundary candidates (10^(l-1) - 1 and 10^l + 1): O(n) for power operations
  • Extracting left half of the string: O(n)
  • The for loop runs 3 iterations (from left - 1 to left + 1):
    • Inside each iteration, the while loop mirrors the left half to create a palindrome
    • The while loop runs O(n/2) times (processing half the digits)
    • Each operation inside the while loop is O(1)
    • Total for palindrome generation: O(n) per iteration
  • Adding elements to set: O(1) average case per addition
  • Discarding element from set: O(1) average case
  • Finding the nearest palindrome by iterating through the set (at most 5 elements): O(1)
  • Converting result to string: O(n)

Overall: O(n) + O(n) + O(n) + 3 * O(n) + O(1) + O(n) = O(n)

Space Complexity: O(n)

  • The set res stores at most 5 candidates: O(1) in terms of count, but each number can be up to n digits long, requiring O(n) space to store
  • Variable left stores half of the input: O(n) space
  • Other variables (x, l, i, j, ans, t) are single integers: O(1) each, but their values can be up to n digits, so O(n) space
  • String slicing n[:(l+1)>>1] creates a temporary string: O(n)

Overall: O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Integer Overflow with Very Large Numbers

One critical pitfall is that Python's int type can handle arbitrarily large numbers, but in other languages like Java or C++, converting very large string numbers to integers can cause overflow issues.

Problem Example:

# This works in Python but would overflow in languages with fixed integer sizes
n = "9" * 100  # A 100-digit number
original_num = int(n)  # This could overflow in other languages

Solution: For extremely large numbers or when porting to other languages, consider using string comparison instead of integer conversion for the final selection:

def compare_distance_using_strings(n: str, candidate: str) -> int:
    # Implement string-based subtraction to avoid overflow
    # This is more complex but necessary for very large numbers
    pass

2. Incorrect Handling of Single-Digit Edge Cases

The formula 10 ** (length - 1) - 1 generates -1 when length = 1, which is incorrect.

Problem Example:

n = "5"
length = 1
candidates = {10 ** (1 - 1) - 1, 10 ** 1 + 1}  # {0, 11}
# But we should also consider "4" and "6" as candidates

Solution: Add special handling for single-digit inputs:

if length == 1:
    digit = int(n)
    if digit == 0:
        return "1"
    elif digit == 9:
        return "8"
    else:
        # Return the closer of digit-1 or digit+1
        candidates = {digit - 1, digit + 1}
        return str(min(candidates, key=lambda x: (abs(x - digit), x)))

3. Missing Edge Case: Numbers Like "99...99"

When the input consists entirely of 9s, the algorithm correctly generates 10^length + 1 (like 1001 for "999"), but the mirroring logic might not generate the optimal smaller palindrome.

Problem Example:

n = "99"
# The algorithm generates candidates: {9, 101, 88, 99, 100}
# It correctly excludes 99 and selects 101
# But we need to ensure 98 is also considered

Solution: The current algorithm actually handles this correctly by including prefix - 1 in the iteration, but developers might accidentally optimize this away thinking it's redundant.

4. Incorrect Mirroring for Even vs Odd Length Numbers

A common mistake is incorrectly determining what to mirror, especially confusing when to include or exclude the middle digit.

Problem Example:

# Incorrect implementation might do:
suffix_to_mirror = prefix_variant // 10  # Always divide by 10
# This would be wrong for even-length numbers

Solution: Always use the correct conditional:

suffix_to_mirror = prefix_variant if length % 2 == 0 else prefix_variant // 10

5. Not Handling the Original Number Being a Palindrome

If the input is already a palindrome, we must find a different palindrome, not return the same number.

Problem Example:

n = "121"  # Already a palindrome
# Must not return "121" again

Solution: The candidates.discard(original_num) line handles this, but it's essential not to forget this step. Some developers might try to optimize by checking if the number is a palindrome first and handling it separately, which can introduce bugs.

6. Tie-Breaking Logic Error

When two palindromes are equidistant, the smaller one should be returned, but the comparison logic can be tricky.

Problem Example:

# Incorrect tie-breaking:
if distance_to_candidate <= distance_to_current_nearest:  # Wrong!
    nearest_palindrome = candidate
# This doesn't ensure we pick the smaller value in case of a tie

Solution: Use explicit tie-breaking logic as shown in the original code:

if (nearest_palindrome == -1 or 
    distance_to_candidate < distance_to_current_nearest or
    (distance_to_candidate == distance_to_current_nearest and 
     candidate < nearest_palindrome)):
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does merge sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More