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: changec
toa
) or"cbc"
(1 operation: changea
toc
). 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.
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:
- Change
s[i]
to matchs[j]
- Change
s[j]
to matchs[i]
- 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:
-
Convert string to list: First, we convert the input string
s
to a list of characterscs = list(s)
. This is necessary because strings in Python are immutable, but we need to modify characters in place. -
Initialize two pointers: Set up two pointers:
i = 0
pointing to the start of the stringj = len(s) - 1
pointing to the end of the string
-
Process pairs of characters: While
i < j
, we process each pair of mirrored characters:- Compare
cs[i]
andcs[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)
- Compare
-
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
- Increment
-
Return the result: Join the modified character list back into a string:
return "".join(cs)
Why this works:
- When
i
meetsj
(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 EvaluatorExample 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'
withcs[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'
withcs[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 thanj
, 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:
- Convert string to list immediately
- Use proper boundary conditions (
left < right
) - Apply
min()
for lexicographic ordering - Return the joined string, not the list or original string
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
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!