Manacher's Algorithm has one single application. It is used to find the Longest Palindromic Sub-string in any string. This algorithm is required to solve sub-problems of some very hard problems. This article explains the basic brute force method first and then moves on to explain the optimized Manacher's Algorithm.
Brute Force Approach:
Find all possible sub-strings using nested loops, this solution has where is the length of the given string. Then for every substring, check if it is a palindrome or not in , so the total time complexity is .
This solution could be improved by selecting every letter in the given string as the center , then find the longest palindromic substring around this center , so the total time complexity is . For example: given string is "", when is selected as the center, it produces the longest palindromic substring "".
However, solution could be improved using some clever observations. Following is the optimized solution.
Manacher's Algorithm
Assume the given String has a length of , = "". Create a string of length , by inserting any letter that doesn't appear in the input string (call it special character for the purpose of this article), in the spaces between any characters. Also, insert this special character in the beginning and the end of the string. If "#" is chosen as special character then the new string would look like "".
Calculate the longest palindromic substring in each center. Expand around each character in the new string, then store the number of letters, in the longest palindromic substring with character as the center, divided by . The stored number is divided by because the palindromic substring has it's same parts around the center.
Above process would yield an array . Each number , in the array , indicates that there are corresponding letters in both sides around a center . So the palindromic substring = letters in the left side + center + letters in right side.
Observation (1):
For the center index i.e which has the longest palindromic substring. Notice that the numbers in array after center are same as numbers before center , so avoid expanding around all letters after center , however just put their values directly using the Mirror (by copying the first half of array in its other half) property.
Observation (2):
Unfortunately, Mirror property can't be applied in all cases. For example: = "", the new string
= "".
The result array = .
= "".
The result array = .
Consider the center . The mirror property applies in . But why ? So Mirror property doesn't work in all cases, because in this case there is another palindrome with center . This new palindrome, with center , goes beyond the limits of the first palindrome with center .
Algorithm Steps:
Let the limits of the first palindrome with center : a left limit , a right limit . have references over the last corresponding letters in the palindrome sub-string. A letter with index in a palindrome substring has a corresponding letter with index such that the = .
(1) If(),
So which means that palindrome with center can't go beyond the original palindrome, so apply the Mirror Property directly.
(2) Else ,
This means that palindrome with center goes beyond the original palindrome, so expanding around this center is needed.
Let , so expanding around center will start from with and so on... because the interval is already contained in the palindrome with center .
The algorithm has nested loops, outer loop check if there will be an expanding around current letter or not. This loop takes steps.
Inner loop will be used in case of expanding around a letter, but it is guaranteed that it takes at most steps by using the above observations.
So the total time = .
Inner loop will be used in case of expanding around a letter, but it is guaranteed that it takes at most steps by using the above observations.
So the total time = .
(3) Update when a palindrome with center goes beyond the original palindrome with center .
as the next expanding will be around center .
as the next expanding will be around center .
Note: Insert another different special characters in the front and the end of string to avoid bound checking.
Implementation:
#include <bits/stdc++.h>
using namespace std;
#define SIZE 100000 + 1
int P[SIZE * 2];
// Transform S into new string with special characters inserted.
string convertToNewString(const string &s) {
string newString = "@";
for (int i = 0; i < s.size(); i++) {
newString += "#" + s.substr(i, 1);
}
newString += "#$";
return newString;
}
string longestPalindromeSubstring(const string &s) {
string Q = convertToNewString(s);
int c = 0, r = 0; // current center, right limit
for (int i = 1; i < Q.size() - 1; i++) {
// find the corresponding letter in the palidrome subString
int iMirror = c - (i - c);
if(r > i) {
P[i] = min(r - i, P[iMirror]);
}
// expanding around center i
while (Q[i + 1 + P[i]] == Q[i - 1 - P[i]]){
P[i]++;
}
// Update c,r in case if the palindrome centered at i expands past r,
if (i + P[i] > r) {
c = i; // next center = i
r = i + P[i];
}
}
// Find the longest palindrome length in p.
int maxPalindrome = 0;
int centerIndex = 0;
for (int i = 1; i < Q.size() - 1; i++) {
if (P[i] > maxPalindrome) {
maxPalindrome = P[i];
centerIndex = i;
}
}
cout << maxPalindrome << "\n";
return s.substr( (centerIndex - 1 - maxPalindrome) / 2, maxPalindrome);
}
int main() {
string s = "kiomaramol\n";
cout << longestPalindromeSubstring(s);
return 0;
}
Không có nhận xét nào:
Đăng nhận xét