31

Given a string (assume only English characters) S of length n, we can count the number of palindromic substrings with the following algorithm:

for i = 0 to |S| do
    p1 = number of palindromes centered in i (odd length)
    p2 = number of palindromes centered in i and i+1 (even length)

    add p1 + p2 to total number of palindromic substrings of S

The above code is O(n^2) however.

I am interested in an algorithm that solves this problem in O(n). I know for sure that one exists as I've heard multiple people say that it does, and the problem exists on a local online judge site with an upper bound of 1 000 000 on n, however I've never seen the algorithm and can't seem to be able to come up with it.

Update:

The general idea I have is to compute len[i] = length of the longest palindrome centered at the character 2i + 1 and a similar array for even-length palindromes. With good bookkeeping, it should be possible to compute this in O(1) for each character, which will allow us to count a lot of palindromes all at once. I'm stuck on how exactly to compute this however.

I will accept a solution that uses O(n) and maybe even O(n log n) extra memory. I think this is impossible without it.

Any good ideas or references are appreciated.

IVlad
  • 43,099
  • 13
  • 111
  • 179
  • What makes you think the solution is O(n) time? Also, it's pretty weird to have an O(n) time algorithm which requires O(n log n) space. – Craig Gidney Sep 06 '10 at 00:54
  • @Strilanc - I think it's O(n) because that's the complexity mentioned by some people and the only thing that could run in 0.1 seconds on a million characters. – IVlad Sep 06 '10 at 11:45
  • Related: [Write a function that returns the longest palindrome in a given string](http://stackoverflow.com/q/1115001/54262) –  Oct 05 '10 at 01:23
  • @IVlad If you figured out how, could you please post the code? – 1110101001 Jan 03 '14 at 22:16

3 Answers3

8

The following site shows an algorithm for computing the longest palindromic substring in O(n) time, and does so by computing the longest palindromic substring at every possible center and then taking the maximum. So, you should be able to easily modify it for your purposes.

http://www.akalin.cx/2007/11/28/finding-the-longest-palindromic-substring-in-linear-time/

EDIT: The first link looks a little shaky upon closer inspection, so here's another one:

http://zhuhcheng.spaces.live.com/Blog/cns!DE38E96268C49F28!311.entry?wa=wsignin1.0&sa=707413829

Paul Accisano
  • 1,416
  • 1
  • 14
  • 25
  • I don't really understand how they calculate P[i] in your second link. Can you clarify on that? All I see are a couple inequalities, but nothing on how to actually compute P. Your first link is a lot clearer in this regard, but some people say it's actually quadratic. I will write my own implementation and test for myself. – IVlad Sep 06 '10 at 11:19
  • 1
    I translated the python code in your first link to C++ and it looks like it's O(n). It runs instantly for a string made up of a single character and it also passes every test I tried. Looks like that's it, thanks! – IVlad Sep 06 '10 at 12:21
  • 4
    It's about the maximun palindrome, and it also skip the small palindrome whenever found a bigger one. I wonder if you was able to count all the palindrome by modify that algorithm? – Chan Le May 12 '11 at 17:52
  • @IVlad I fond an error in the python code on line 70. The `else:` statement seems out of place and I'm not sure about its proper place. – mbadawi23 Jun 26 '13 at 17:32
  • @IVlad How did you modify the algorithm to count the number of palindromes? – 1110101001 Jan 03 '14 at 22:16
  • [Longest Palindromic Substring](http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html) This link is really very good. And have a very good explanation. PS. link mentioned in previous answers does not exist anymore. – aitrom May 26 '14 at 19:46
  • 1
    @Paul: I don't think the first link is shaky, they are both descriptions of the same algorithm, Manacher's Algorithm. – simonzack Sep 11 '14 at 00:46
1

For "normal" strings it should be rather efficient to look at each character as the potential "center" of a palindrome and then check if the surrounding characters actually build one:

# check odd palindromes
for center in range(len(ls)):
   # check how many characters to the left and right of |center|
   # build a palindrome
   maxoffs = min(center, len(ls)-center-1)
   offs = 0
   while offs <= maxoffs and ls[center-offs] == ls[center+offs]:
      offs += 1
   offs -= 1
   print ls[center-offs : center+offs+1]                                    

# check for even palindromes
for center in range(len(ls)-1):
   maxoffs = min(center, len(ls)-center-2)
   offs = 0
   while offs <= maxoffs and ls[center-offs] == ls[center+offs+1]:
      offs += 1
   offs -= 1
   if offs >= 0:
      print ls[center-offs : center+offs+2]

For normal strings this should be about O(n), though in the worst case, for example if the string consists of only one character repeated over and over again, it will still take O(n2) time.

sth
  • 222,467
  • 53
  • 283
  • 367
  • 1
    You can indeed stop the search early, which will be good enough for random strings. I'm interested in something that's always `O(n)` however. It's very easy to break this: a string made up of a single character. – IVlad Sep 05 '10 at 20:05
1

Consider a string S="aaabb".

Append a character '$' at both ends of the string and in between every two consecutive characters to change the string to S="$a$a$a$b$b$" and apply Manacher's algorithm for this string S.

New string S is of length 2n+1 which gives us runtime of O(2n+1) which is same as O(n).

index :  1 2 3 4 5 6 7 8 9 10 11
A     :  1 3 5 7 5 3 1 3 5  3  1
S     :  $ a $ a $ a $ b $  b  $

Array A is the result of Manacher's Algorithm.

Now, the summation of A[i]/4 for index where '$', else (A[i]+1)/4 for every other character from 1<=i<=n is your answer.

Here, $ acts as a center for the even length palidromic substrings and the odd length can be calculated normally. The answer for this case is:

0 + 1 + 1 + 2 + 1 + 1 + 0 + 1 + 1 + 1 + 0 = 9 (a,a,aaa,a,b,b,aa,aa,bb).

giusti
  • 3,156
  • 3
  • 29
  • 44