After spending about 6-8 hours trying to digest the Manacher\'s algorithm, I am ready to throw in the towel. But before I do, here is one last shot in the dark: can anyone e
The Algorithm on this site seems understandable to the certain point http://www.akalin.cx/longest-palindrome-linear-time
To understand this particular approach the best is to try to solving the problem on paper and catching the tricks you can implement to avoid checking for the palindrome for each possible center.
First answer yourself - when you find a palindrome of a given length, let's say 5 - can't you as a next step just jump to the end of this palindrome (skipping 4 letters and 4 mid-letters)?
If you try to create a palindrome with length 8 and place another palindrome with length > 8, which center is in the right side of the first palindrome you will notice something funny. Try it out: Palindrome with length 8 - WOWILIKEEKIL - Like + ekiL = 8 Now in most cases you would be able to write down the place between two E's as a center and number 8 as the length and jump after the last L to look for the center of the bigger palindrome.
This approach is not correct, which the center of bigger palindrome can be inside ekiL and you would miss it if you would jump after the last L.
After you find LIKE+EKIL you place 8 in the array that these algos use and this looks like:
[0,1,0,3,0,1,0,1,0,3,0,1,0,1,0,1,8]
for
[#,W,#,O,#,W,#,I,#,L,#,I,#,K,#,E,#]
The trick is that you already know that most probably next 7 (8-1) numbers after 8 will be the same as on the left side, so the next step is to automatically copy 7 numbers from left of 8 to right of 8 keeping in mind they are not yet final. The array would look like this
[0,1,0,3,0,1,0,1,0,3,0,1,0,1,0,1,8,1,0,1,0,1,0,3] (we are at 8)
for
[#,W,#,O,#,W,#,I,#,L,#,I,#,K,#,E,#,E,#,K,#,I,#,L]
Let's make an example, that such jump would destroy our current solution and see what we can notice.
WOWILIKEEKIL - lets try to make bigger palindrome with the center somewhere within EKIL. But its not possible - we need to change word EKIL to something that contain palindrome. What? OOOOOh - thats the trick. The only possibility to have a bigger palindrome with the center in the right side of our current palindrome is that it is already in the right (and left) side of palindrome.
Let's try to build one based on WOWILIKEEKIL We would need to change EKIL to for example EKIK with I as a center of the bigger palindrome - remember to change LIKE to KIKE as well. First letters of our tricky palindrome will be:
WOWIKIKEEKIK
as said before - let the last I be the center of the bigger pallindrome than KIKEEKIK:
WOWIKIKEEKIKEEKIKIW
let's make the array up to our old pallindrom and find out how to laverage the additional info.
for
[_ W _ O _ W _ I _ K _ I _ K _ E _ E _ K _ I _ K _ E _ E _ K _ I _ K _ I _ W ]
it will be [0,1,0,3,0,1,0,1,0,3,0,3,0,1,0,1,8
we know that the next I - a 3rd will be the longest pallindrome, but let's forget about it for a bit. lets copy the numbers in the array from the left of 8 to the right (8 numbers)
[0,1,0,3,0,1,0,1,0,3,0,3,0,1,0,1,8,1,0,1,0,3,0,3]
In our loop we are at between E's with number 8. What is special about I (future middle of biggest pallindrome) that we cannot jump right to K (the last letter of currently biggest pallindrome)? The special thing is that it exceeds the current size of the array ... how? If you move 3 spaces to the right of 3 - you are out of array. It means that it can be the middle of the biggest pallindrome and the furthest you can jump is this letter I.
Sorry for the length of this answer - I wanted to explain the algorythm and can assure you - @OmnipotentEntity was right - I understand it even better after explaining to you :)
I went through the same frustration/struggle and I found the solution on this page, https://www.hackerearth.com/practice/algorithms/string-algorithm/manachars-algorithm/tutorial/, to be easiest to understand. I tried to implement this solution in my own style, and I think I can understand the algorithm now. I also tried to stuff as many explanations in the code as possible to explain the algo. Hope this help!
#Manacher's Algorithm
def longestPalindrome(s):
s = s.lower()
#Insert special characters, #, between characters
#Insert another special in the front, $, and at the end, @, of string to avoid bound checking.
s1 = '$#'
for c in s:
s1 += c + '#'
s1 = s1+'@'
#print(s, " -modified into- ", s1)
#Palin[i] = length of longest palindrome start at center i
Palin = [0]*len(s1)
#THE HARD PART: THE MEAT of the ALGO
#c and r help keeping track of the expanded regions.
c = r = 0
for i in range(1,len(s1)-1): #NOTE: this algo always expands around center i
#if we already expanded past i, we can retrieve partial info
#about this location i, by looking at the mirror from left side of center.
if r > i: #---nice, we look into memory of the past---
#look up mirror from left of center c
mirror = c - (i-c)
#mirror's largest palindrome = Palin[mirror]
#case1: if mirror's largest palindrome expands past r, choose r-i
#case2: if mirror's largest palindrome is contains within r, choose Palin[mirror]
Palin[i] = min(r-i, Palin[mirror])
#keep expanding around center i
#NOTE: instead of starting to expand from i-1 and i+1, which is repeated work
#we start expanding from Palin[i],
##which is, if applicable, updated in previous step
while s1[i+1+Palin[i]] == s1[i-1-Palin[i]]:
Palin[i] += 1
#if expanded past r, update r and c
if i+Palin[i] > r:
c = i
r = i + Palin[i]
#the easy part: find the max length, remove special characters, and return
max_center = max_length = 0
for i in range(len(s1)):
if Palin[i] > max_length:
max_length = Palin[i]
max_center = i
output = s1[max_center-max_length : max_center+max_length]
output = ''.join(output.split('#'))
return output # for the (the result substring)
I agree that the logic isn't quite right in the explanation of the link. I give some details below.
Manacher's algorithm fills in a table P[i] which contains how far the palindrome centered at i extends. If P[5]=3, then three characters on either side of position five are part of the palindrome. The algorithm takes advantage of the fact that if you've found a long palindrome, you can fill in values of P on the right side of the palindrome quickly by looking at the values of P on the left side, since they should mostly be the same.
I'll start by explaining the case you were talking about, and then I'll expand this answer as needed.
R indicates the index of the right side of the palindrome centered at C. Here is the state at the place you indicated:
C=11
R=20
i=15
i'=7
P[i']=7
R-i=5
and the logic is like this:
if P[i']<=R-i: // not true
else: // P[i] is at least 5, but may be greater
The pseudo-code in the link indicates that P[i] should be greater than or equal to P[i'] if the test fails, but I believe it should be greater than or equal to R-i, and the explanation backs that up.
Since P[i'] is greater than R-i, the palindrome centered at i' extends past the palindrome centered at C. We know the palindrome centered at i will be at least R-i characters wide, because we still have symmetry up to that point, but we have to search explicitly beyond that.
If P[i'] had been no greater than R-i, then the largest palindrome centered at i' is within the largest palindrome centered at C, so we would have known that P[i] couldn't be any larger than P[i']. If it was, we would have a contradiction. It would mean that we would be able to extend the palindrome centered at i beyond P[i'], but if we could, then we would also be able to extend the palindrome centered at i' due to the symmetry, but it was already supposed to be as large as possible.
This case is illustrated previously:
C=11
R=20
i=13
i'=9
P[i']=1
R-i=7
In this case, P[i']<=R-i. Since we are still 7 characters away from the edge of the palindrome centered at C, we know that at least 7 characters around i are the same as the 7 characters around i'. Since there was only a one character palindrome around i', there is a one character palindrome around i as well.
j_random_hacker noticed that the logic should be more like this:
if P[i']<R-i then
P[i]=P[i']
else if P[i']>R-i then
P[i]=R-i
else P[i]=R-i + expansion
If P[i'] < R-i, then we know that P[i]==P[i'], since we're still inside the palindrome centered at C.
If P[i'] > R-i, then we know that P[i]==R-i, because otherwise the palindrome centered at C would have extended past R.
So the expansion is really only necessary in the special case where P[i']==R-i, so we don't know if the palindrome at P[i] may be longer.
This is handled in the actual code by setting P[i]=min(P[i'],R-i) and then always expanding. This way of doing it doesn't increase the time complexity, because if no expansion is necessary, the time taken to do the expansion is constant.
I made a video on this Algorithm using Animation Graphics. Hopefully it will help you understand it! https://www.youtube.com/watch?v=kbUiR5YWUpQ
const lpal = str => {
let lpal = ""; // to store longest palindrome encountered
let pal = ""; // to store new palindromes found
let left; // to iterate through left side indices of the character considered to be center of palindrome
let right; // to iterate through left side indices of the character considered to be center of palindrome
let j; // to iterate through all characters and considering each to be center of palindrome
for (let i=0; i<str.length; i++) { // run through all characters considering them center of palindrome
pal = str[i]; // initializing current palindrome
j = i; // setting j as index at the center of palindorme
left = j-1; // taking left index of j
right = j+1; // taking right index of j
while (left >= 0 && right < str.length) { // while left and right indices exist
if(str[left] === str[right]) { //
pal = str[left] + pal + str[right];
} else {
break;
}
left--;
right++;
}
if(pal.length > lpal.length) {
lpal = pal;
}
pal = str[i];
j = i;
left = j-1;
right = j+1;
if(str[j] === str[right]) {
pal = pal + str[right];
right++;
while (left >= 0 && right < str.length) {
if(str[left] === str[right]) {
pal = str[left] + pal + str[right];
} else {
break;
}
left--;
right++;
}
if(pal.length > lpal.length) {
lpal = pal;
}
}
}
return lpal;
}
console.log(lpal("gerngehgbrgregbeuhgurhuygbhsbjsrhfesasdfffdsajkjsrngkjbsrjgrsbjvhbvhbvhsbrfhrsbfsuhbvsuhbvhvbksbrkvkjb"));
asdfffdsa
Full Article: http://www.zrzahid.com/longest-palindromic-substring-in-linear-time-manachers-algorithm/
First of all lets observe closely to a palindrome in order to find some interesting properties. For example, S1 = "abaaba" and S2="abcba", both are palindrome but what is the non-trivial (i.e. not length or characters) difference between them? S1 is a palindrome centered around the invisible space between i=2 and i=3 (non-existent space!). On the other hand S2 is centered around character at i=2 (ie. c). In order to graciously handle the center of a palindrome irrespective of the odd/even length, lets transform the palindrome by inserting special character $ in between characters. Then S1="abba" and S2="abcba" will be transformed into T1="$a$b$a$a$b$a$" centered at i=6 and T2="$a$b$c$b$a$" centered at i=5. Now, we can see that centers are existent and lengths are consistent 2*n+1, where n=length of original string. For example,
i' c i ----------------------------------------------------- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| ----------------------------------------------------- T1=| $ | a | $ | b | $ | a | $ | a | $ | b | $ | a | $ | -----------------------------------------------------
Next, observe that from the symmetric property of a (transformed) palindrome T around the center c, T[c-k] = T[c+k] for 0<= k<= c. That is positions c-k and c+k are mirror to each other. Let's put it another way, for an index i on the right of center c, the mirror index i' is on the left of c such that c-i'=i-c => i'=2*c-i and vice versa. That is,
For each position i on the right of center c of a palindromic substring, the mirror position of i is, i'=2*c-i, and vice versa.
Let us define an array P[0..2*n] such that P[i] equals to the length of the palindrome centered at i. Note that, length is actually measured by number of characters in the original string (by ignoring special chars $). Also let min and max be respectively the leftmost and rightmost boundary of a palindromic substring centered at c. So, min=c-P[c] and max=c+P[c]. For example, for palindrome S="abaaba", the transformed palindrome T, mirror center c=6, length array P[0..12], min=c-P[c]=6-6=0, max=c+P[c]=6+6=12 and two sample mirrored indices i and i' are shown in the following figure.
min i' c i max ----------------------------------------------------- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| ----------------------------------------------------- T=| $ | a | $ | b | $ | a | $ | a | $ | b | $ | a | $ | ----------------------------------------------------- P=| 0 | 1 | 0 | 3 | 0 | 5 | 6 | 1 | 0 | 3 | 0 | 1 | 0 | -----------------------------------------------------
With such a length array P, we can find the length of longest palindromic substring by looking into the max element of P. That is,
P[i] is the length of a palindromic substring with center at i in the transformed string T, ie. center at i/2 in the original string S; Hence the longest palindromic substring would be the substring of length P[imax] starting from index (imax-P[imax])/2 such that imax is the index of maximum element in P.
Let us draw a similar figure in the following for our non-palindromic example string S="babaabca".
min c max |----------------|-----------------| -------------------------------------------------------------------- idx= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16| --------------------------------------------------------------------- T=| $ | b | $ | a | $ | b | $ | a | $ | a | $ | b | $ | c | $ | a | $ | --------------------------------------------------------------------- P=| 0 | 1 | 0 | 3 | 0 | 3 | 0 | 1 | 4 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | ---------------------------------------------------------------------
Question is how to compute P efficiently. The symmetric property suggests the following cases that we could potentially use to compute P[i] by using previously computed P[i'] at the mirror index i' on the left, hence skipping a lot of computations. Let's suppose that we have a reference palindrome (first palindrome) to start with.
A third palindrome whose center is within the right side of a first palindrome will have exactly the same length as that of a second palindrome anchored at the mirror center on the left side, if the second palindrome is within the bounds of the first palindrome by at least one character.For example in the following figure with the first palindrome centered at c=8 and bounded by min=4 and max=12, length of the third palindrome centered at i=9 (with mirror index i'= 2*c-i = 7) is, P[i] = P[i'] = 1. This is because the second palindrome centered at i' is within the bounds of first palindrome. Similarly, P[10] = P[6] = 0.
|----3rd----| |----2nd----| |-----------1st Palindrome---------| min i' c i max |------------|---|---|-------------| -------------------------------------------------------------------- idx= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16| --------------------------------------------------------------------- T=| $ | b | $ | a | $ | b | $ | a | $ | a | $ | b | $ | c | $ | a | $ | --------------------------------------------------------------------- P=| 0 | 1 | 0 | 3 | 0 | 3 | 0 | 1 | 4 | ? | ? | ? | ? | ? | ? | ? | ? | ---------------------------------------------------------------------Now, question is how to check this case? Note that, due to symmetric property length of segment [min..i'] is equals to the length of segment [i..max]. Also, note that 2nd palindrome is completely within 1st palindrome iff left edge of the 2nd palindrome is inside the left boundary, min of the 1st palindrome. That is,
i'-P[i'] >= min =>P[i']-i' < -min (negate) =>P[i'] < i'-min =>P[i'] < max-i [(max-i)=(i'-min) due to symmetric property].Combining all the facts in case 1,
P[i] = P[i'], iff (max-i) > P[i']
If the second palindrome meets or extends beyond the left bound of the first palindrome, then the third palindrome is guaranteed to have at least the length from its own center to the right outermost character of the first palindrome. This length is the same from the center of the second palindrome to the left outermost character of the first palindrome.For example in the following figure, second palindrome centered at i=5 extends beyond the left bound of the first palindrome. So, in this case we can't say P[i]=P[i']. But length of the third palindrome centered at i=11, P[i] is at least the length from its center i=11 to the right bound max=12 of first palindrome centered at c. That is, P[i]>=1. This means third palindrome could be extended past max if and only if next immediate character past max matches exactly with the mirrored character, and we continue this check beyond. For example, in this case P[13]!=P[9] and it can't be extended. So, P[i] = 1.
|-------2nd palindrome------| |----3rd----|---? |-----------1st Palindrome---------| min i' c i max |----|-----------|-----------|-----| -------------------------------------------------------------------- idx= | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12| 13| 14| 15| 16| --------------------------------------------------------------------- T=| $ | b | $ | a | $ | b | $ | a | $ | a | $ | b | $ | c | $ | a | $ | --------------------------------------------------------------------- P=| 0 | 1 | 0 | 3 | 0 | 3 | 0 | 1 | 4 | 1 | 0 | ? | ? | ? | ? | ? | ? | ---------------------------------------------------------------------So, how to check this case? This is simply the failed check for case 1. That is, second palindrome will extend past left edge of first palindrome iff,
i'-P[i'] < min =>P[i']-i' >= -min [negate] =>P[i'] >= i'-min =>P[i'] >= max-i [(max-i)=(i'-min) due to symmetric property].That is, P[i] is at least (max-i) iff (max-i) P[i]>=(max-i), iff (max-i) Now, if the third palindrome does extend beyond max then we need to update the center and the boundary of the new palindrome.
If the palindrome centered at i does expand past max then we have new (extended) palindrome, hence a new center at c=i. Update max to the rightmost boundary of the new palindrome.Combining all the facts in case 1 and case 2, we can come up with a very beautiful little formulae:
Case 1: P[i] = P[i'], iff (max-i) > P[i'] Case 2: P[i]>=(max-i), iff (max-i) = min(P[i'], max-i).
That is, P[i]=min(P[i'], max-i) when the third palindrome is not extendable past max. Otherwise, we have new third palindrome at center at c=i and new max=i+P[i].
Neither the first nor second palindrome provides information to help determine the palindromic length of a fourth palindrome whose center is outside the right side of the first palindrome.That is, we can't determine preemptively P[i] if i>max. That is,
P[i] = 0, iff max-i < 0Combining all the cases, we conclude the formulae:
P[i] = max>i ? min(P[i'], max-i) : 0. In case we can expand beyond max then we expand by matching characters beyond max with the mirrored character with respect to new center at c=i. Finally when we have a mismatch we update new max=i+P[i].
Reference: algorithm description in wiki page