1842. Next Palindrome Using Same Digits π
Problem Description
You are given a numeric string num
that represents a very large palindrome (a number that reads the same forwards and backwards).
Your task is to find the smallest palindrome that is larger than num
and can be created by rearranging the digits of num
. If no such palindrome exists, return an empty string ""
.
The key insight is that since the input is already a palindrome, we need to find the next lexicographically larger arrangement of its digits that still forms a palindrome.
The solution works by focusing on just the first half of the palindrome. Since a palindrome is symmetric, once we determine what the first half should be, the second half is automatically determined (it mirrors the first half).
The algorithm:
- Extract the first half of the palindrome string
- Find the next permutation of this first half (the next lexicographically larger arrangement)
- If a next permutation exists, mirror it to create the second half, forming a complete palindrome
- If no next permutation exists (meaning the first half is already in descending order), return
""
For example, if num = "1221"
, the first half is "12"
. The next permutation of "12"
is "21"
, so the resulting palindrome would be "2112"
.
The next_permutation
function implements the classic algorithm to find the next lexicographically larger permutation by:
- Finding the rightmost position where a digit can be increased
- Swapping it with the smallest digit to its right that is larger than it
- Reversing the remaining digits to get the smallest possible arrangement
Intuition
Since we're dealing with a palindrome, we know that the second half is completely determined by the first half - it's just a mirror image. This means we don't need to consider all the digits independently; we only need to focus on rearranging the first half.
The key realization is that finding the "smallest palindrome larger than num
" is equivalent to finding the next lexicographically larger arrangement of the first half. Why? Because once we have the next arrangement of the first half, mirroring it automatically gives us the next palindrome.
Think about it this way: if we have a palindrome like "1221"
, the first half is "12"
and it determines the entire palindrome. To get the next larger palindrome, we need the first half to become "21"
, which then creates "2112"
when mirrored.
But how do we ensure it's the smallest palindrome larger than the original? This is where the "next permutation" algorithm comes in. The next permutation gives us the very next arrangement in lexicographical order - there's no smaller arrangement between the current one and the next permutation. This guarantees we get the smallest possible larger palindrome.
The reason we can't just randomly rearrange digits is that we need to maintain the property that our result is larger than the original while being as small as possible. The next permutation algorithm achieves this by:
- Finding the rightmost position where we can make an increase
- Making the smallest possible increase at that position
- Arranging the remaining digits in ascending order to minimize the overall value
If no next permutation exists (when the first half is already in descending order like "321"
), then we've exhausted all possible arrangements, and no larger palindrome can be formed by rearranging the digits.
Learn more about Two Pointers patterns.
Solution Approach
The solution implements the strategy of finding the next permutation of the first half of the palindrome string. Let's walk through the implementation step by step:
Main Function Structure:
The solution consists of two parts:
- A helper function
next_permutation
that finds the next lexicographically larger arrangement - The main logic that applies this to create the palindrome
Next Permutation Algorithm:
The next_permutation
function works on the first half of the digits:
-
Find the pivot point (
i
): Starting from the second-to-last position of the first half (n - 2
), scan leftward to find the first position wherenums[i] < nums[i + 1]
. This is the rightmost position where we can make an increase.i = n - 2 while i >= 0 and nums[i] >= nums[i + 1]: i -= 1
-
Check if permutation exists: If
i < 0
, all digits are in descending order, meaning we already have the largest possible permutation. ReturnFalse
. -
Find the swap candidate (
j
): From the end of the first half, find the smallest digit that is still larger thannums[i]
.j = n - 1 while j >= 0 and nums[j] <= nums[i]: j -= 1
-
Swap and reverse: Swap
nums[i]
withnums[j]
, then reverse all digits after positioni
to get the smallest arrangement of the remaining digits.nums[i], nums[j] = nums[j], nums[i] nums[i + 1 : n] = nums[i + 1 : n][::-1]
Creating the Palindrome:
Once we have the next permutation of the first half:
- Convert the input string to a list for easier manipulation
- Apply
next_permutation
to get the next arrangement - If successful, mirror the first half to create the second half:
for i in range(n // 2): nums[n - i - 1] = nums[i]
- Join the list back into a string and return
The algorithm handles both even and odd-length palindromes correctly because:
- For even-length palindromes, we work with exactly half the digits
- For odd-length palindromes, the middle digit remains unchanged, and we still only need to rearrange the first half
Time Complexity: O(n)
where n
is the length of the string
Space Complexity: O(n)
for storing the list representation of the string
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 num = "12321"
, a 5-digit palindrome.
Step 1: Extract the first half
- Length = 5, so first half length = 5 // 2 = 2
- First half: positions [0, 1] = "12"
- Convert entire string to list: ['1', '2', '3', '2', '1']
Step 2: Find next permutation of first half
We need to find the next permutation of indices [0, 1]:
a) Find pivot point (i):
- Start at i = 0 (n-2 where n=2)
- Check: nums[0] < nums[1]? β '1' < '2'? Yes!
- Found pivot at i = 0
b) Find swap candidate (j):
- Start at j = 1 (n-1 where n=2)
- Check: nums[1] > nums[0]? β '2' > '1'? Yes!
- Found swap candidate at j = 1
c) Perform swap:
- Swap nums[0] and nums[1]
- List becomes: ['2', '1', '3', '2', '1']
d) Reverse after pivot:
- Reverse from position i+1 to n-1 (positions 1 to 1)
- Only one element, so no change
- List remains: ['2', '1', '3', '2', '1']
Step 3: Mirror the first half to create palindrome
- Copy position 0 to position 4: nums[4] = '2'
- Copy position 1 to position 3: nums[3] = '1'
- Middle element (position 2) stays unchanged
- Final list: ['2', '1', '3', '1', '2']
Step 4: Convert back to string
- Result: "21312"
Verification:
- Original: "12321"
- Result: "21312"
- Is it a palindrome? Yes (reads same forwards and backwards)
- Is it larger? Yes (21312 > 12321)
- Is it the smallest larger palindrome? Yes (next permutation guarantees this)
Edge Case Example:
If num = "32123"
:
- First half: "32"
- Already in descending order (3 β₯ 2)
- No next permutation exists
- Return: ""
This demonstrates why the algorithm correctly identifies when no larger palindrome can be formed by rearrangement.
Solution Implementation
1class Solution:
2 def nextPalindrome(self, num: str) -> str:
3 """
4 Find the next palindrome by rearranging the digits of the given number.
5
6 Args:
7 num: A string representing a palindromic number
8
9 Returns:
10 The next palindrome that can be formed by rearranging digits,
11 or empty string if no such palindrome exists
12 """
13
14 def next_permutation(nums: list[str]) -> bool:
15 """
16 Find the next lexicographically greater permutation of the first half.
17 This modifies the list in-place.
18
19 Args:
20 nums: List of digit characters (only first half is considered)
21
22 Returns:
23 True if next permutation exists, False otherwise
24 """
25 # Only work with the first half of the palindrome
26 n = len(nums) // 2
27
28 # Step 1: Find the rightmost position i where nums[i] < nums[i+1]
29 # This is the position that needs to be increased
30 i = n - 2
31 while i >= 0 and nums[i] >= nums[i + 1]:
32 i -= 1
33
34 # If no such position exists, we've reached the largest permutation
35 if i < 0:
36 return False
37
38 # Step 2: Find the rightmost position j where nums[j] > nums[i]
39 # This will be swapped with position i
40 j = n - 1
41 while j >= 0 and nums[j] <= nums[i]:
42 j -= 1
43
44 # Step 3: Swap the elements at positions i and j
45 nums[i], nums[j] = nums[j], nums[i]
46
47 # Step 4: Reverse the suffix starting at position i+1
48 # This gives us the smallest permutation greater than the original
49 nums[i + 1 : n] = nums[i + 1 : n][::-1]
50
51 return True
52
53 # Convert string to list for in-place modifications
54 nums = list(num)
55
56 # Try to find the next permutation of the first half
57 if not next_permutation(nums):
58 return ""
59
60 # Mirror the first half to create a palindrome
61 n = len(nums)
62 for i in range(n // 2):
63 nums[n - i - 1] = nums[i]
64
65 # Convert back to string and return
66 return "".join(nums)
67
1class Solution {
2 /**
3 * Finds the next palindrome with the same set of digits
4 * @param num - input palindrome string
5 * @return next larger palindrome with same digits, or empty string if none exists
6 */
7 public String nextPalindrome(String num) {
8 // Convert string to character array for manipulation
9 char[] nums = num.toCharArray();
10
11 // Try to find next permutation of the first half
12 if (!nextPermutation(nums)) {
13 // No next permutation exists
14 return "";
15 }
16
17 // Mirror the first half to the second half to maintain palindrome property
18 int n = nums.length;
19 for (int i = 0; i < n / 2; ++i) {
20 nums[n - 1 - i] = nums[i];
21 }
22
23 // Convert back to string and return
24 return String.valueOf(nums);
25 }
26
27 /**
28 * Generates the next lexicographically greater permutation of the first half
29 * @param nums - character array representing the palindrome
30 * @return true if next permutation exists, false otherwise
31 */
32 private boolean nextPermutation(char[] nums) {
33 // Only consider the first half of the palindrome
34 int n = nums.length / 2;
35
36 // Step 1: Find the rightmost position where nums[i] < nums[i+1]
37 int i = n - 2;
38 while (i >= 0 && nums[i] >= nums[i + 1]) {
39 --i;
40 }
41
42 // If no such position exists, we have the largest permutation
43 if (i < 0) {
44 return false;
45 }
46
47 // Step 2: Find the smallest element to the right of position i that is greater than nums[i]
48 int j = n - 1;
49 while (j >= 0 && nums[i] >= nums[j]) {
50 --j;
51 }
52
53 // Step 3: Swap elements at positions i and j
54 swap(nums, i, j);
55 i++;
56
57 // Step 4: Reverse the sequence from position i+1 to the end of first half
58 j = n - 1;
59 while (i < j) {
60 swap(nums, i, j);
61 i++;
62 j--;
63 }
64
65 return true;
66 }
67
68 /**
69 * Swaps two characters in the array
70 * @param nums - character array
71 * @param i - first index
72 * @param j - second index
73 */
74 private void swap(char[] nums, int i, int j) {
75 char temp = nums[i];
76 nums[i] = nums[j];
77 nums[j] = temp;
78 }
79}
80
1class Solution {
2public:
3 string nextPalindrome(string num) {
4 int n = num.size();
5
6 // Extract the left half of the palindrome (excluding middle if odd length)
7 string leftHalf = num.substr(0, n / 2);
8
9 // Try to find the next lexicographically greater permutation of the left half
10 // If no such permutation exists, return empty string
11 if (!next_permutation(begin(leftHalf), end(leftHalf))) {
12 return "";
13 }
14
15 // Mirror the new left half to create the next palindrome
16 // Copy left half to both left and right sides of the result
17 for (int i = 0; i < n / 2; ++i) {
18 num[i] = leftHalf[i]; // Set left side character
19 num[n - i - 1] = leftHalf[i]; // Mirror to right side character
20 }
21
22 return num;
23 }
24};
25
1/**
2 * Finds the next palindrome by finding the next permutation of the first half
3 * and mirroring it to create a palindrome
4 * @param num - The input number as a string
5 * @returns The next palindrome as a string, or empty string if not possible
6 */
7function nextPalindrome(num: string): string {
8 // Convert string to array of characters for manipulation
9 const digits: string[] = num.split('');
10 const length: number = digits.length;
11
12 // Try to get the next permutation of the first half
13 if (!nextPermutation(digits)) {
14 return '';
15 }
16
17 // Mirror the first half to the second half to create a palindrome
18 // Copy each digit from the first half to its corresponding position in the second half
19 for (let i = 0; i < length >> 1; ++i) {
20 digits[length - 1 - i] = digits[i];
21 }
22
23 // Convert array back to string and return
24 return digits.join('');
25}
26
27/**
28 * Modifies the first half of the array to get the next lexicographically greater permutation
29 * @param digits - Array of digit characters to permute (modified in place)
30 * @returns true if next permutation exists, false otherwise
31 */
32function nextPermutation(digits: string[]): boolean {
33 // Only consider the first half of the digits for permutation
34 const halfLength: number = digits.length >> 1;
35
36 // Step 1: Find the rightmost digit that is smaller than its next digit
37 // This is the pivot point for creating the next permutation
38 let pivotIndex: number = halfLength - 2;
39 while (pivotIndex >= 0 && digits[pivotIndex] >= digits[pivotIndex + 1]) {
40 pivotIndex--;
41 }
42
43 // If no such digit exists, we're at the largest permutation
44 if (pivotIndex < 0) {
45 return false;
46 }
47
48 // Step 2: Find the smallest digit to the right of pivot that is larger than pivot
49 let swapIndex: number = halfLength - 1;
50 while (swapIndex >= 0 && digits[pivotIndex] >= digits[swapIndex]) {
51 swapIndex--;
52 }
53
54 // Step 3: Swap the pivot with the found digit
55 [digits[pivotIndex], digits[swapIndex]] = [digits[swapIndex], digits[pivotIndex]];
56
57 // Step 4: Reverse the suffix after the pivot position to get the smallest permutation
58 let left: number = pivotIndex + 1;
59 let right: number = halfLength - 1;
60 while (left < right) {
61 [digits[left], digits[right]] = [digits[right], digits[left]];
62 left++;
63 right--;
64 }
65
66 return true;
67}
68
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the input string.
- The
next_permutation
function performs operations on the first half of the string (lengthn/2
):- The first while loop iterates at most
n/2
times to find the pivot point:O(n/2)
- The second while loop iterates at most
n/2
times to find the swap position:O(n/2)
- The swap operation is
O(1)
- The reversal operation
nums[i + 1 : n][::-1]
affects at mostn/2
elements:O(n/2)
- The first while loop iterates at most
- After
next_permutation
, the for loop runsn/2
times to mirror the first half to the second half:O(n/2)
- The
join
operation to create the final string isO(n)
All these operations sum to O(n/2) + O(n/2) + O(n/2) + O(n/2) + O(n) = O(n)
.
The space complexity is O(n)
, where n
is the length of the input string.
- Converting the string to a list creates a new list of size
n
:O(n)
- The slicing operation
nums[i + 1 : n][::-1]
creates a temporary list of at mostn/2
elements:O(n/2)
- The final string creation with
join
requiresO(n)
space
The dominant space usage is O(n)
for storing the list representation of the string.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Handling Odd-Length Palindromes
A common mistake is assuming that the middle character in odd-length palindromes needs special treatment during the next permutation calculation. This can lead to incorrect boundary calculations or attempting to include the middle character in the permutation logic.
Pitfall Example:
# Incorrect: Trying to include middle character in permutation
n = len(nums) // 2 + (len(nums) % 2) # Wrong boundary for odd-length
Solution:
The algorithm correctly uses n = len(nums) // 2
for both even and odd-length palindromes. The middle character (if it exists) remains unchanged and doesn't participate in the permutation:
n = len(nums) // 2 # Correct for both even and odd lengths
2. Off-by-One Errors in Index Calculations
When mirroring the first half to create the palindrome, it's easy to make indexing errors, especially with the relationship between i
and its mirror position n - i - 1
.
Pitfall Example:
# Incorrect mirroring
for i in range(n // 2):
nums[n - i] = nums[i] # Wrong: should be n - i - 1
Solution: Use the correct mirror index formula:
for i in range(n // 2):
nums[n - i - 1] = nums[i] # Correct mirroring
3. Not Preserving Original Input When No Solution Exists
Some implementations might partially modify the input array before determining that no next permutation exists, leading to corrupted results.
Pitfall Example:
def next_permutation(nums):
# Modifying nums directly without checking if solution exists first
nums[0], nums[1] = nums[1], nums[0] # Premature modification
# Then checking if valid...
Solution:
The algorithm correctly checks for the existence of a valid pivot point (i
) before making any modifications. If i < 0
, it returns False
immediately without changing the array.
4. Forgetting to Reverse the Suffix After Swapping
A critical step in the next permutation algorithm is reversing the suffix after the swap to ensure we get the smallest possible next permutation. Omitting this step results in a larger-than-necessary palindrome.
Pitfall Example:
# After swapping nums[i] and nums[j] nums[i], nums[j] = nums[j], nums[i] # Forgot to reverse the suffix - results in wrong answer
Solution: Always reverse the suffix after the swap:
nums[i], nums[j] = nums[j], nums[i] nums[i + 1 : n] = nums[i + 1 : n][::-1] # Essential step
5. String vs List Confusion
Attempting to modify a string directly (strings are immutable in Python) or forgetting to convert back to string at the end.
Pitfall Example:
# Trying to modify string directly num[i] = num[j] # Error: strings are immutable
Solution: Convert to list for manipulation, then back to string:
nums = list(num) # Convert to mutable list
# ... perform operations ...
return "".join(nums) # Convert back to string
What is an advantages of top-down dynamic programming vs bottom-up dynamic programming?
Recommended Readings
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!