Facebook Pixel

2697. Lexicographically Smallest Palindrome

Problem Description

You are given a string s consisting of lowercase English letters. You can perform operations on this string where each operation allows you to replace any character with another lowercase English letter.

Your goal is to transform s into a palindrome using the minimum number of operations. A palindrome reads the same forwards and backwards (like "racecar" or "noon").

If there are multiple palindromes that can be created with the same minimum number of operations, you must return the lexicographically smallest one. A string is lexicographically smaller than another if, at the first position where they differ, it has a character that comes earlier in the alphabet.

For example:

  • If s = "abc", you can make it a palindrome by changing it to "aba" (1 operation: change c to a) or "cbc" (1 operation: change a to c). Since both require the same number of operations, you should return "aba" because it's lexicographically smaller.

The solution uses a two-pointer approach, comparing characters from both ends of the string. For each pair of characters at positions i and j (where i starts from the beginning and j from the end), both characters are replaced with the smaller one between them. This ensures both the minimum number of operations and the lexicographically smallest result.

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

Intuition

To make a string a palindrome, we need characters at mirrored positions to be equal. For any string of length n, character at position i must equal character at position n-1-i.

When we look at two characters that need to match (like s[i] and s[j] where j = n-1-i), we have three choices:

  1. Change s[i] to match s[j]
  2. Change s[j] to match s[i]
  3. Change both to some third character

The key insight is that changing both characters to a third character requires 2 operations, while making one match the other requires only 1 operation (unless they're already equal, which requires 0 operations).

So for minimum operations, we should always choose option 1 or 2 - make one character match the other.

But which one should we choose? Since we also want the lexicographically smallest result, we should always choose the smaller character between s[i] and s[j]. For example, if s[i] = 'c' and s[j] = 'a', we should change both positions to 'a' rather than 'c'.

This greedy strategy works because:

  • Each pair of positions is independent - our choice for one pair doesn't affect the optimal choice for another pair
  • Choosing the smaller character at each step guarantees the lexicographically smallest result overall
  • We're using the minimum operations since we're only changing at most one character per mismatched pair

By processing all pairs from outside to inside using two pointers, we ensure every position is considered exactly once and the resulting string is both a palindrome and lexicographically minimal.

Learn more about Greedy and Two Pointers patterns.

Solution Approach

The solution implements a greedy approach with two pointers to efficiently create the lexicographically smallest palindrome.

Implementation Steps:

  1. Convert string to list: First, we convert the input string s to a list of characters cs = list(s). This is necessary because strings in Python are immutable, but we need to modify characters in place.

  2. Initialize two pointers: Set up two pointers:

    • i = 0 pointing to the start of the string
    • j = len(s) - 1 pointing to the end of the string
  3. Process pairs of characters: While i < j, we process each pair of mirrored characters:

    • Compare cs[i] and cs[j]
    • Set both positions to the minimum of the two characters: cs[i] = cs[j] = min(cs[i], cs[j])
    • This ensures both positions have the same character (making it palindromic) and it's the smaller one (making it lexicographically smallest)
  4. Move pointers inward: After processing each pair:

    • Increment i by 1: i = i + 1
    • Decrement j by 1: j = j - 1
    • Continue until the pointers meet or cross
  5. Return the result: Join the modified character list back into a string: return "".join(cs)

Why this works:

  • When i meets j (odd-length string), the middle character doesn't need modification as it's already palindromic by itself
  • Each pair is processed exactly once, ensuring O(n/2) character comparisons
  • The min() function handles both the palindrome requirement and lexicographic minimization in one operation

Time Complexity: O(n) where n is the length of the string - we visit each character once Space Complexity: O(n) for storing the character list (needed since strings are immutable in Python)

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 the string s = "egcfe".

Step 1: Convert to character list

  • cs = ['e', 'g', 'c', 'f', 'e']
  • Initialize pointers: i = 0, j = 4

Step 2: First pair comparison (i=0, j=4)

  • Compare cs[0] = 'e' with cs[4] = 'e'
  • min('e', 'e') = 'e'
  • Set both positions to 'e': cs = ['e', 'g', 'c', 'f', 'e'] (no change needed)
  • Move pointers: i = 1, j = 3

Step 3: Second pair comparison (i=1, j=3)

  • Compare cs[1] = 'g' with cs[3] = 'f'
  • min('g', 'f') = 'f'
  • Set both positions to 'f': cs = ['e', 'f', 'c', 'f', 'e']
  • Move pointers: i = 2, j = 2

Step 4: Pointers meet (i=2, j=2)

  • Since i is not less than j, we stop
  • The middle character 'c' remains unchanged (it's already palindromic by itself)

Step 5: Return result

  • Join the list: "efcfe"
  • This is a palindrome that reads the same forwards and backwards

Operations count: We made 1 operation (changing 'g' to 'f' at position 1).

Why this is optimal:

  • We needed at least 1 operation since positions 1 and 3 had different characters
  • By choosing 'f' over 'g', we got the lexicographically smallest palindrome possible with 1 operation
  • Alternative would be "egcge" (changing 'f' to 'g'), but 'f' < 'g', so our solution is better

Solution Implementation

1class Solution:
2    def makeSmallestPalindrome(self, s: str) -> str:
3        """
4        Creates the lexicographically smallest palindrome by modifying the input string.
5      
6        For each pair of characters at symmetric positions (i, n-1-i), 
7        both characters are replaced with the lexicographically smaller one.
8      
9        Args:
10            s: Input string to be transformed into a palindrome
11          
12        Returns:
13            The lexicographically smallest palindrome that can be formed
14        """
15        # Convert string to list for in-place modification
16        char_list = list(s)
17      
18        # Initialize two pointers: left starting from beginning, right from end
19        left = 0
20        right = len(s) - 1
21      
22        # Process pairs of characters from outside to center
23        while left < right:
24            # Replace both characters with the lexicographically smaller one
25            # This ensures the result is both a palindrome and lexicographically smallest
26            char_list[left] = char_list[right] = min(char_list[left], char_list[right])
27          
28            # Move pointers towards the center
29            left += 1
30            right -= 1
31      
32        # Convert list back to string and return
33        return "".join(char_list)
34
1class Solution {
2    public String makeSmallestPalindrome(String s) {
3        // Convert string to character array for in-place modification
4        char[] characters = s.toCharArray();
5      
6        // Use two pointers approach: left pointer starts from beginning, right from end
7        int left = 0;
8        int right = characters.length - 1;
9      
10        // Process pairs of characters from both ends moving towards center
11        while (left < right) {
12            // To create the lexicographically smallest palindrome,
13            // replace both characters in the pair with the smaller one
14            char minChar = (char) Math.min(characters[left], characters[right]);
15            characters[left] = minChar;
16            characters[right] = minChar;
17          
18            // Move pointers towards center
19            left++;
20            right--;
21        }
22      
23        // Convert character array back to string and return
24        return new String(characters);
25    }
26}
27
1class Solution {
2public:
3    string makeSmallestPalindrome(string s) {
4        // Use two pointers approach: left pointer starts from beginning, right from end
5        int left = 0;
6        int right = s.size() - 1;
7      
8        // Process characters from both ends moving towards the center
9        while (left < right) {
10            // To create the lexicographically smallest palindrome,
11            // replace both characters at symmetric positions with the smaller one
12            char minChar = min(s[left], s[right]);
13            s[left] = minChar;
14            s[right] = minChar;
15          
16            // Move pointers towards the center
17            left++;
18            right--;
19        }
20      
21        return s;
22    }
23};
24
1/**
2 * Creates the lexicographically smallest palindrome by modifying the input string.
3 * For each pair of characters at positions i and j (where i + j = length - 1),
4 * replaces both characters with the lexicographically smaller one.
5 * 
6 * @param s - The input string to be transformed into a palindrome
7 * @returns The lexicographically smallest palindrome that can be formed
8 */
9function makeSmallestPalindrome(s: string): string {
10    // Convert string to character array for easier manipulation
11    const characters: string[] = s.split('');
12  
13    // Use two pointers approach: left pointer starts from beginning, right from end
14    let leftIndex: number = 0;
15    let rightIndex: number = s.length - 1;
16  
17    // Process pairs of characters until pointers meet in the middle
18    while (leftIndex < rightIndex) {
19        // Choose the lexicographically smaller character between the pair
20        const smallerChar: string = s[leftIndex] < s[rightIndex] ? s[leftIndex] : s[rightIndex];
21      
22        // Set both positions to the smaller character to ensure palindrome property
23        characters[leftIndex] = smallerChar;
24        characters[rightIndex] = smallerChar;
25      
26        // Move pointers towards the center
27        leftIndex++;
28        rightIndex--;
29    }
30  
31    // Join the character array back into a string
32    return characters.join('');
33}
34

Time and Space Complexity

The time complexity is O(n), where n is the length of the string. The algorithm uses a two-pointer approach that starts from both ends of the string and moves toward the center. The while loop runs for n/2 iterations since i starts at 0 and j starts at n-1, and they meet in the middle. Each iteration performs constant-time operations (comparison with min() and assignment), resulting in O(n/2) which simplifies to O(n).

The space complexity is O(n), where n is the length of the string. The code creates a list cs from the input string s using list(s), which requires O(n) additional space to store all characters. The final "".join(cs) operation also creates a new string of length n, but this is typically considered part of the output rather than auxiliary space. The other variables (i and j) use constant space O(1).

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

Common Pitfalls

1. Attempting to Modify String Directly

A common mistake is trying to modify the string in place without converting it to a list first:

# WRONG - This will raise TypeError
def makeSmallestPalindrome(self, s: str) -> str:
    left, right = 0, len(s) - 1
    while left < right:
        s[left] = s[right] = min(s[left], s[right])  # ERROR: strings are immutable
        left += 1
        right -= 1
    return s

Solution: Always convert the string to a mutable data structure (list) before attempting modifications.

2. Incorrect Pointer Boundary Condition

Using left <= right instead of left < right in the while loop:

# WRONG - Unnecessary processing when pointers meet
while left <= right:  # Should be left < right
    char_list[left] = char_list[right] = min(char_list[left], char_list[right])
    left += 1
    right -= 1

Why it's wrong: When left == right (middle character in odd-length strings), we're comparing and replacing a character with itself, which is redundant. While this doesn't produce incorrect results, it adds unnecessary operations.

3. Choosing Maximum Instead of Minimum

A conceptual error where developers might use max() thinking they need to preserve more information:

# WRONG - Creates lexicographically larger palindrome
char_list[left] = char_list[right] = max(char_list[left], char_list[right])

Solution: Always use min() to ensure the lexicographically smallest result. Remember that 'a' < 'b' < 'c' in lexicographic order.

4. Forgetting to Return Modified String

Returning the original string or the list instead of the joined result:

# WRONG - Returns list instead of string
return char_list  # Should be "".join(char_list)

# WRONG - Returns original unmodified string
return s  # Should be "".join(char_list)

5. Off-by-One Error in Pointer Initialization

Incorrectly initializing the right pointer:

# WRONG - right pointer goes out of bounds
right = len(s)  # Should be len(s) - 1

Solution: Remember that string indices are 0-based, so the last valid index is len(s) - 1.

Correct Implementation Pattern

To avoid these pitfalls, follow this pattern:

  1. Convert string to list immediately
  2. Use proper boundary conditions (left < right)
  3. Apply min() for lexicographic ordering
  4. Return the joined string, not the list or original string
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More