Minimum Number of Moves to Make Palindrome
HardPrompt
You are given a string s consisting only of lowercase English letters.
In one move, you can select any two adjacent characters of s and swap them.
Return the minimum number of moves needed to make s a palindrome.
Note that the input will be generated such that s can always be converted to a palindrome.
Example 1:
Input: s = "aabb"
Output: 2
Explanation:
We can obtain two palindromes from s, "abba" and "baab".
- We can obtain "abba" from s in 2 moves: "aabb" -> "abab" -> "abba".
- We can obtain "baab" from s in 2 moves: "aabb" -> "abab" -> "baab".
Thus, the minimum number of moves needed to make s a palindrome is 2.Example 2:
Input: s = "letelt"
Output: 2
Explanation:
One of the palindromes we can obtain from s in 2 moves is "lettel".
One of the ways we can obtain it is "letelt" -> "letetl" -> "lettel".
Other palindromes such as "tleelt" can also be obtained in 2 moves.
It can be shown that it is not possible to obtain a palindrome in less than 2 moves.
Constraints:
1 <= s.length <= 2000sconsists only of lowercase English letters.scan be converted to a palindrome using a finite number of moves.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a greedy strategy with two pointers, left and right, to build the palindrome from the outside in. We iterate as long as left is less than right. In each step, we try to match the character at the left pointer with a corresponding character and move it to the right pointer's position. If the character at left is the unique middle character of the palindrome (meaning it has no pair), we simply swap it with its right neighbor to shift it towards the center and re-evaluate.
Algorithm
- Initialize
moves = 0. - Convert the input string
sto a mutable representation, like a character arrayarr. - Use two pointers,
left = 0andright = s.length() - 1. - Loop while
left < right: a. Ifarr[left]andarr[right]are the same, they form a valid pair. Shrink the window by incrementingleftand decrementingright. b. If they are different, find the rightmost indexkof the characterarr[left]within the current window[left, right]. c. Case 1: A pair is found (k != left). This meansarr[k]is the matching character forarr[left]. We need to movearr[k]to therightposition. This requiresright - kadjacent swaps. Add this number tomoves, perform the swaps (by bubbling the character atkto the right), and then shrink the window (left++,right--). d. Case 2: No pair is found (k == left). This implies thatarr[left]is the unique middle character of the palindrome. Since it has no pair, we cannot match it. Instead, we swap it with its adjacent characterarr[left+1]to move it out of the way. This costs 1 move. We do not shrink the window, as we haven't formed a pair yet. The loop continues, and in the next iteration, the newarr[left]will be processed.
Walkthrough
The core idea is to iteratively fix the outermost characters of the string to form a palindrome. We use a character array for easy in-place modifications.
We maintain two pointers, left and right, starting at the ends of the array.
- If
arr[left]andarr[right]are already a match, we've found a pair for our palindrome, so we can shrink our problem space by moving the pointers inwards. - If they don't match, we are committed to making
arr[left]the character for the current outer pair. We search for its matching partner from the right side of the array (fromright-1down toleft). - If we find a partner at index
k, we calculate the number of adjacent swaps needed to move it to therightposition, which isright - k. We add this to our total moves and perform the swaps. After this,arr[left]andarr[right]match, so we shrink the pointers. - A special case occurs when the search for a partner for
arr[left]only finds itself (k == left). This indicatesarr[left]is the character that will end up in the middle of the final odd-length palindrome. Since it can't be paired, we perform a minimal action to get it out of theleftposition: we swap it witharr[left+1], add 1 to our move count, and let the loop continue from the sameleftandrightpointers. This process effectively nudges the middle character towards the center one step at a time while other pairs are being formed.
class Solution { public int minMovesToMakePalindrome(String s) { char[] arr = s.toCharArray(); int n = arr.length; int left = 0; int right = n - 1; int moves = 0; while (left < right) { if (arr[left] == arr[right]) { left++; right--; continue; } // Find the rightmost character that matches arr[left] int k = right - 1; while (k > left && arr[k] != arr[left]) { k--; } if (k == left) { // arr[left] is the middle character // Swap with its right neighbor char temp = arr[left]; arr[left] = arr[left + 1]; arr[left + 1] = temp; moves++; } else { // Found a pair for arr[left] at index k // Move character from k to right for (int i = k; i < right; i++) { char temp = arr[i]; arr[i] = arr[i + 1]; arr[i + 1] = temp; moves++; } left++; right--; } } return moves; }}Complexity
Time
O(N^2), where N is the length of the string. The `while` loop runs about N/2 times. Inside the loop, finding the matching character `k` can take up to O(N) time, and the subsequent swaps can also take O(N) time in the worst case.
Space
O(N), where N is the length of the string. This is required to create a mutable character array from the immutable input string.
Trade-offs
Pros
The logic is relatively straightforward to understand and implement.
It correctly solves the problem by making locally optimal moves.
It modifies the string representation in-place (using a char array), which simplifies state management.
Cons
The time complexity is quadratic, which might be slow for very large inputs, although it passes within the given constraints.
Handling the middle character by single swaps can be less efficient than directly placing it or working around it.
This approach is a more refined greedy strategy. At each step where the outer characters s[left] and s[right] do not match, we evaluate two possible greedy moves. The first option is to find the matching pair for s[left] and move it to the right end. The second option is to find the matching pair for s[right] and move it to the left end. We calculate the number of swaps required for both options and execute the one that is cheaper. This ensures that we make the most efficient move at each step to form the palindrome's outer layers.
Algorithm
- Initialize
moves = 0. - Convert the input string
sto a character arrayarr. - Use two pointers,
left = 0andright = s.length() - 1. - Loop while
left < right: a. Ifarr[left] == arr[right], shrink the window (left++,right--). b. Otherwise, we have two choices to form the outer pair: i. Matcharr[left]: Find its rightmost partner at indexk. The cost isright - kswaps. ii. Matcharr[right]: Find its leftmost partner at indexj. The cost isj - leftswaps. c. Compare the costs. If movingarr[left]'s partner is cheaper or equal (right - k <= j - left), then:- Add
right - ktomoves. - Bubble the character from
ktoright. d. Otherwise (movingarr[right]'s partner is cheaper): - Add
j - lefttomoves. - Bubble the character from
jtoleft. e. After the swaps, a pair is formed at the boundaries. Shrink the window (left++,right--).
- Add
Walkthrough
This method also builds the palindrome from the outside in but makes a more informed greedy choice at each step. When arr[left] and arr[right] differ, we don't just default to matching arr[left]. Instead, we calculate the 'cost' of two alternative actions:
- Match
arr[left]: We find the rightmost occurrence ofarr[left]'s character, say at indexk. The cost to move this character to positionrightisright - kswaps. - Match
arr[right]: We find the leftmost occurrence ofarr[right]'s character, say at indexj. The cost to move this character to positionleftisj - leftswaps.
We then choose the action with the minimum cost. If right - k is less than or equal to j - left, we perform the first action. Otherwise, we perform the second. After the chosen character is moved and the pair is formed, we shrink the window by incrementing left and decrementing right.
This approach elegantly handles the middle character case. If arr[left] is the middle character, its only occurrence is at left, so k will be left. The cost right - k will be large. The other character arr[right] must have a pair, and the cost j - left to move its pair will likely be smaller, leading the algorithm to correctly choose the second, more efficient option.
class Solution { public int minMovesToMakePalindrome(String s) { char[] arr = s.toCharArray(); int n = arr.length; int left = 0; int right = n - 1; int moves = 0; while (left < right) { if (arr[left] == arr[right]) { left++; right--; continue; } // Find rightmost match for left char int k = right - 1; while (k > left && arr[k] != arr[left]) { k--; } // Find leftmost match for right char int j = left + 1; while (j < right && arr[j] != arr[right]) { j++; } // Choose the cheaper move if ((right - k) <= (j - left)) { moves += (right - k); // Move char from k to right for (int i = k; i < right; i++) { char temp = arr[i]; arr[i] = arr[i + 1]; arr[i + 1] = temp; } } else { moves += (j - left); // Move char from j to left for (int i = j; i > left; i--) { char temp = arr[i]; arr[i] = arr[i - 1]; arr[i - 1] = temp; } } left++; right--; } return moves; }}Complexity
Time
O(N^2). Although it makes smarter choices, the asymptotic complexity remains the same. The `while` loop runs N/2 times, and each iteration involves two linear scans (O(N)) and a series of swaps (O(N)).
Space
O(N), for the mutable character array.
Trade-offs
Pros
More efficient in practice than the simple greedy approach by always making the locally optimal choice.
Handles the middle character case more elegantly and directly without special logic.
Guaranteed to find the minimum number of moves due to the nature of the greedy choice.
Cons
The time complexity is still quadratic, same as the simpler greedy approach.
The implementation is slightly more complex due to considering two options at each step.
Solutions
Solution
class Solution {public int minMovesToMakePalindrome(String s) { int n = s.length(); int ans = 0; char[] cs = s.toCharArray(); for (int i = 0, j = n - 1; i < j; ++i) { boolean even = false; for (int k = j; k != i; --k) { if (cs[i] == cs[k]) { even = true; for (; k < j; ++k) { char t = cs[k]; cs[k] = cs[k + 1]; cs[k + 1] = t; ++ans; } --j; break; } } if (!even) { ans += n / 2 - i; } } return ans; }}class Solution {public: int minMovesToMakePalindrome(string s) { int n = s.size(); int ans = 0; for (int i = 0, j = n - 1; i < j; ++i) { bool even = false; for (int k = j; k != i; --k) { if (s[i] == s[k]) { even = true; for (; k < j; ++k) { swap(s[k], s[k + 1]); ++ans; } --j; break; } } if (!even) ans += n / 2 - i; } return ans; }};class Solution: def minMovesToMakePalindrome(self, s: str) -> int: cs = list(s) ans, n = 0, len(s) i, j = 0, n - 1 while i < j: even = False for k in range(j, i, - 1): if cs[i] == cs[k]: even = True while k < j: cs[k], cs[k + 1] = cs[k + 1], cs[k] k += 1 ans += 1 j -= 1 break if not even: ans += n // 2 - i i += 1 return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.