Problem
You are given a string s
. You can convert s
to a palindrome by adding characters in front of it.
Return the shortest palindrome you can find by performing this transformation.
OR
Given a string S, you are allowed to convert it to a palindrome by adding characters in front of it. Find and return the shortest palindrome you can find by performing this transformation.
Examples
Example 1:
Input: s = "aacecaaa"
Output: "aaacecaaa"
Example 2:
Input: s = "abcd"
Output: "dcbabcd"
Example 3:
Input: s = "dedcba"
Output: "abcdedcba"
Solution
Method 1 - Brute Force
A brute force way to solve this problem is to keep adding characters from last one by one at front and keep checking whether current string is palindrome or not, at max we need to check characters equal to half of length of string because in worst case half of the string need to be added at front to make string palindrome.
Code
Java
public class Solution {
public String shortestPalindrome(String s) {
int n = s.length();
// Brute-force approach to find the longest palindromic prefix
int longestPalindromePrefixEnd = 0;
for (int i = n; i >= 1; i--) {
if (isPalindrome(s.substring(0, i))) {
longestPalindromePrefixEnd = i;
break;
}
}
// Construct the shortest palindrome
String suffix = s.substring(longestPalindromePrefixEnd);
String prefix = new StringBuilder(suffix).reverse().toString();
return prefix + s;
}
// Helper method to check if a given string is a palindrome
public boolean isPalindrome(String str) {
int left = 0, right = str.length() - 1;
while (left < right) {
if (str.charAt(left) != str.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}
Python
class Solution:
def shortestPalindrome(self, s: str) -> str:
n = len(s)
# Brute-force approach to find the longest palindromic prefix
longest_palindrome_prefix_end = 0
for i in range(n, 0, -1):
if self.is_palindrome(s[:i]):
longest_palindrome_prefix_end = i
break
# Construct the shortest palindrome
suffix = s[longest_palindrome_prefix_end:]
prefix = suffix[::-1]
return prefix + s
# Helper method to check if a given string is a palindrome
def is_palindrome(self, str: str) -> bool:
left, right = 0, len(str) - 1
while left < right:
if str[left] != str[right]:
return False
left += 1
right -= 1
return True
Complexity
- ⏰ Time complexity:
O(n^2)
- 🧺 Space complexity:
O(1)
Method 2 - Get the Suffix Which is Not Palindrome
Consider Java, answer for it will be avaJava.
We can start from the end, check if start and end don’t match, that is the problematic substring. If they match, it is good and can be part of suffix.
Code
Java
class Solution {
public String shortestPalindrome(String s) {
int i = 0;
int j = s.length() - 1;
while (j >= 0) {
if (s.charAt(i) == s.charAt(j)) {
i++;
}
j--;
}
if (i == s.length()) {
return s;
}
String suffix = s.substring(i);
String prefix = new StringBuilder(suffix).reverse().toString();
String mid = shortestPalindrome(s.substring(0, i));
return prefix + mid + suffix;
}
}
Python
Dry Run
Dry running the above code:
s = Java, i = 0, j = 3
s[i] != s[j] => j = 2
s[i] != s[j] => j = 1
s[i] != s[j] => j = 0
s[i] == s[j] => j = -1, i =1
So, only substring we can take as mid = s[0:i] = J
suffix = s[i:] = ava
prefix = ava as well.
Answer is avaJava.
Method 3 - Rabin Karp Algorithm
The problem asks us to find the shortest palindrome by prepending characters to the front of the string. We can use the Rabin-Karp algorithm to find the longest palindromic prefix efficiently.
Here are the steps:
- Initialize the variables for hash computation.
- Iterate through the string to calculate the hash values for the original string and compare them with the suffix’s hash values.
- Find the length of the longest palindromic prefix.
- Prepend the necessary characters from the reverse of the suffix and construct the shortest palindrome.
Code
Java
public class Solution {
public String shortestPalindrome(String s) {
int n = s.length();
int palindromePrefixEnd = -1;
long base = 29;
long mod = 1000000007;
long power = 1;
long prefixHash = 0;
long suffixHash = 0;
for (int i = 0; i < n; i++, power = power * base % mod) {
// Update prefix hash (left-to-right)
prefixHash = (prefixHash * base + s.charAt(i) - 'a' + 1) % mod;
// Update suffix hash (right-to-left by reverse hash logic)
suffixHash = (suffixHash + (s.charAt(i) - 'a' + 1) * power) % mod;
// Check if prefix and suffix hashes match
if (prefixHash == suffixHash) {
palindromePrefixEnd = i;
}
}
// Get the characters to prepend and form the result
String suffixToAdd =
new StringBuilder(s.substring(palindromePrefixEnd + 1))
.reverse()
.toString();
return suffixToAdd + s;
}
}
Python
class Solution:
def shortestPalindrome(self, s: str) -> str:
n = len(s)
base = 29
mod = 1000000007
# Initialize hashes and power
prefix_hash = 0
suffix_hash = 0
power = 1
palindrome_prefix_end = -1
for i in range(n):
# Update prefix hash (left-to-right)
prefix_hash = (prefix_hash * base + ord(s[i]) - ord('a') + 1) % mod
# Update suffix hash (right-to-left)
suffix_hash = (suffix_hash + (ord(s[i]) - ord('a') + 1) * power) % mod
# Update power for next iteration
power = power * base % mod
# Check if current prefix is also a suffix (i.e., potential palindrome)
if prefix_hash == suffix_hash:
palindrome_prefix_end = i
# Form the shortest palindrome
suffix_to_add = s[palindrome_prefix_end + 1:][::-1]
return suffix_to_add + s
Complexity
- ⏰ Time complexity:
O(n)
- 🧺 Space complexity:
O(n)
Method 4 - Scan From Left and Right finding palindrome in center
We can solve this problem by using one of the methods which is used to solve the longest palindrome substring problem.
Specifically, we can start from the center and scan two sides. If read the left boundary, then the shortest palindrome is identified.
Code
Java
public String shortestPalindrome(String s) {
if (s == null || s.length()<= 1)
return s;
String result = null;
int len = s.length();
int mid = len / 2;
for (int i = mid; i >= 1; i--) {
if (s.charAt(i) == s.charAt(i - 1)) {
if ((result = scanFromCenter(s, i - 1, i)) != null)
return result;
} else {
if ((result = scanFromCenter(s, i - 1, i - 1)) != null)
return result;
}
}
return result;
}
private String scanFromCenter(String s, int l, int r) {
int i = 1;
//scan from center to both sides
for (; l - i >= 0; i++) {
if (s.charAt(l - i) != s.charAt(r + i))
break;
}
//if not end at the beginning of s, return null
if (l - i >= 0)
return null;
StringBuilder sb = new StringBuilder(s.substring(r + i));
sb.reverse();
return sb.append(s).toString();
}