22

If the input is 'abba' then the possible palindromes are a, b, b, a, bb, abba.
I understand that determining if string is palindrome is easy. It would be like:

public static boolean isPalindrome(String str) {
 int len = str.length();
 for(int i=0; i<len/2; i++) {
     if(str.charAt(i)!=str.charAt(len-i-1) {
         return false;
     }
 return true;  
}

But what is the efficient way of finding palindrome substrings?

rolfl
  • 17,539
  • 7
  • 42
  • 76
Himanshu Yadav
  • 13,315
  • 46
  • 162
  • 291
  • using your example, would you expect to get "bab" and "baab" too? – Daniel Kaplan Nov 05 '13 at 23:23
  • I would have expected not, since `bab` and `baab` is not a part of the String unless you change the order of the characters first. – Simon Forsberg Nov 05 '13 at 23:24
  • it's not an efficient way, but you can take every substring, and check whether it's a palindrome. It would only take O(n^3) time – Sam I am says Reinstate Monica Nov 05 '13 at 23:26
  • Is the input always a palindrome? – Samuel O'Malley Nov 05 '13 at 23:26
  • So you can change the character order as you like? that seems to me like a quite large number of possible palindromes as the string goes in size. – Valentin Ruano Nov 05 '13 at 23:26
  • @ValentinRuano I think character order can not be changed. – Himanshu Yadav Nov 05 '13 at 23:28
  • @SamIam Are you sure? Can it be better than O(n^3)? – Himanshu Yadav Nov 05 '13 at 23:30
  • @SamuelO'Malley Lets assume that. It was not mentioned in the question. – Himanshu Yadav Nov 05 '13 at 23:31
  • 1
    "possible palindromes are a, b, b, a, bb, abba" So we can count some of them twice based on their position in the original string? This looks like it could simplify the problem greatly. – Zong Nov 05 '13 at 23:32
  • 4
    Perhaps you could iterate across potential middle character (odd length palindromes) and middle points between characters (even length palindromes) and extend each until you cannot get any further (next left and right characters don't match). That would save a lot of computation when there are no many palidromes in the string. In such case the cost would be O(n) for sparse palidrome strings. For palindrome dense would be O(n^2) as each position cannot be extended more than the length of the array / 2. Obviously this is even less towards the ends of the array. – Valentin Ruano Nov 05 '13 at 23:35
  • Single letter can not be a palindrome as palindrome can be only a word, phrase or verse. – Damian Leszczyński - Vash Nov 05 '13 at 23:38
  • @Vash single letter *is* a palindrome. So is empty string. – Michał Rybak Nov 05 '13 at 23:42
  • @MichałRybak, Some of characters could be considered as letter, a I o for example. But not all of them and for sure an empty string can not be stated as palindrome as it is not existing in alphabet. – Damian Leszczyński - Vash Nov 05 '13 at 23:55
  • [Not All One-letter words can be palindrome](http://digitalcommons.butler.edu/wordways/vol44/iss4/13/) – Damian Leszczyński - Vash Nov 05 '13 at 23:57
  • I was talking about letters. Definition of palindrome is (not being too formal) *looks the same when read from either start to end or the other way*. This *definitely* applies to empty string, too. Note that empty string is a subset of every set of strings. – Michał Rybak Nov 05 '13 at 23:59
  • The definition of palindrome is whatever the professor who assigned the assignment says it is. :) :) :) – ajb Nov 06 '13 at 00:03
  • @Vash's link: sorry, but when we program, we should think as computers ;) and `'D` is two characters for computer, not one. When I said *single letter* I meant *one character between a and z*. – Michał Rybak Nov 06 '13 at 00:04
  • @MichałRybak, Then Oxford dictionary states is really clear what a palindrome is. It states that you can read it. Regarding then comment about apostrophe D it seam that you did not catch the abstract correctly. But this is irrelevant as ajb pointed out. But what you should know is that computers do not think, they only execute statements. Beside you never wrote `one letter word` but `single letter`. – Damian Leszczyński - Vash Nov 06 '13 at 00:14
  • I've understood the paper that you've linked to. Did your read the [full paper](http://digitalcommons.butler.edu/cgi/viewcontent.cgi?article=3151&context=wordways) or just the abstract? because you seem not to understand my comment. Anyway, what I wanted to point out is that there is a difference between what we call a *one letter word* in natural language and what is considered to be a *single letter* (= assignable to `char`) by computers. And the question is about computer program, so I think we can safely assume that we talk about `char`s. – Michał Rybak Nov 06 '13 at 00:31

9 Answers9

33

This can be done in O(n), using Manacher's algorithm.

The main idea is a combination of dynamic programming and (as others have said already) computing maximum length of palindrome with center in a given letter.


What we really want to calculate is radius of the longest palindrome, not the length. The radius is simply length/2 or (length - 1)/2 (for odd-length palindromes).

After computing palindrome radius pr at given position i we use already computed radiuses to find palindromes in range [i - pr ; i]. This lets us (because palindromes are, well, palindromes) skip further computation of radiuses for range [i ; i + pr].

While we search in range [i - pr ; i], there are four basic cases for each position i - k (where k is in 1,2,... pr):

  • no palindrome (radius = 0) at i - k
    (this means radius = 0 at i + k, too)
  • inner palindrome, which means it fits in range
    (this means radius at i + k is the same as at i - k)
  • outer palindrome, which means it doesn't fit in range
    (this means radius at i + k is cut down to fit in range, i.e because i + k + radius > i + pr we reduce radius to pr - k)
  • sticky palindrome, which means i + k + radius = i + pr
    (in that case we need to search for potentially bigger radius at i + k)

Full, detailed explanation would be rather long. What about some code samples? :)

I've found C++ implementation of this algorithm by Polish teacher, mgr Jerzy Wałaszek.
I've translated comments to english, added some other comments and simplified it a bit to be easier to catch the main part.
Take a look here.


Note: in case of problems understanding why this is O(n), try to look this way:
after finding radius (let's call it r) at some position, we need to iterate over r elements back, but as a result we can skip computation for r elements forward. Therefore, total number of iterated elements stays the same.

Edward
  • 5,148
  • 2
  • 29
  • 42
Michał Rybak
  • 8,648
  • 3
  • 42
  • 54
  • +1 Thanks. It is clear that dynamic algorithm for filling array of `radius`es is O(n), however I am not super sure anymore that iterating over all possible palindromes represented by this array is O(n) too. – kiruwka Nov 06 '13 at 08:55
  • 1
    Do you mean the part that prints results? This cannot be `O(n)`, as results (printed out as strings) are of size `O(n^2)` - consider string "aaaaaaaaaa", results [here](http://codepad.org/Nr1OGAfq). Hovewer, array itself is `O(n)` size and contains all data neccessary to print out the palindromes. Which representation of results to choose (just print the array in `O(n)` or print all palindromes in `O(n^2)`?) is totally separate from the algorithm itself. – Michał Rybak Nov 06 '13 at 09:41
  • Thanks for a detailed answer. It has definitely helped me to understand it. – Himanshu Yadav Nov 11 '13 at 19:30
  • 3
    I personally don't think this answers OP's question - as this is the algorithm for "longest palindromic substring", not "list all substrings that are palindromes". – PoweredByRice Feb 21 '14 at 09:26
  • @Nhan please analyze the algorithm more carefully. It first finds longest palindromic substring *at given position*, then uses it to find palindromes *inside* the longest one. Then it searches for longest palindromic substring at next *not yet analyzed* position... and so on. Try to draw the steps on paper and it will become crystal clear. – Michał Rybak Feb 21 '14 at 13:44
18

Perhaps you could iterate across potential middle character (odd length palindromes) and middle points between characters (even length palindromes) and extend each until you cannot get any further (next left and right characters don't match).

That would save a lot of computation when there are no many palidromes in the string. In such case the cost would be O(n) for sparse palidrome strings.

For palindrome dense inputs it would be O(n^2) as each position cannot be extended more than the length of the array / 2. Obviously this is even less towards the ends of the array.

  public Set<String> palindromes(final String input) {

     final Set<String> result = new HashSet<>();

     for (int i = 0; i < input.length(); i++) {
         // expanding even length palindromes:
         expandPalindromes(result,input,i,i+1);
         // expanding odd length palindromes:
         expandPalindromes(result,input,i,i);
     } 
     return result;
  }

  public void expandPalindromes(final Set<String> result, final String s, int i, int j) {
      while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)) {
            result.add(s.substring(i,j+1));
            i--; j++;
      }
  }
Valentin Ruano
  • 2,726
  • 19
  • 29
  • Nice. Note that in the OP's original example, this would return "a" and "b" only once. If it's necessary to track each occurrence, it's easy enough to define a class that contains both a `String` and a starting position, then make `result` a `Set` of that instead of `Set`. – ajb Nov 05 '13 at 23:52
  • A map return where the key is the string and the value the (e.g. the first found) start point would do nicely. – Valentin Ruano Nov 05 '13 at 23:56
  • 1
    s[i] == s[j] this statement is wrong.. how you are getting character from string based on index? – Rahul Sahu Jun 20 '15 at 20:07
  • @RahulSahu thanks for the observation, I've never compiled the code so is likely to have these small problems. – Valentin Ruano Jun 21 '15 at 19:30
9

So, each distinct letter is already a palindrome - so you already have N + 1 palindromes, where N is the number of distinct letters (plus empty string). You can do that in single run - O(N).

Now, for non-trivial palindromes, you can test each point of your string to be a center of potential palindrome - grow in both directions - something that Valentin Ruano suggested.
This solution will take O(N^2) since each test is O(N) and number of possible "centers" is also O(N) - the center is either a letter or space between two letters, again as in Valentin's solution.

Note, there is also O(N) solution to your problem, based on Manacher's algoritm (article describes "longest palindrome", but algorithm could be used to count all of them)

kiruwka
  • 9,250
  • 4
  • 30
  • 41
5

I just came up with my own logic which helps to solve this problem. Happy coding.. :-)

System.out.println("Finding all palindromes in a given string : ");
        subPal("abcacbbbca");

private static void subPal(String str) {
        String s1 = "";
        int N = str.length(), count = 0;
        Set<String> palindromeArray = new HashSet<String>();
        System.out.println("Given string : " + str);
        System.out.println("******** Ignoring single character as substring palindrome");
        for (int i = 2; i <= N; i++) {
            for (int j = 0; j <= N; j++) {
                int k = i + j - 1;
                if (k >= N)
                    continue;
                s1 = str.substring(j, i + j);
                if (s1.equals(new StringBuilder(s1).reverse().toString())) {
                    palindromeArray.add(s1);
                }
            }

        }
        System.out.println(palindromeArray);
        for (String s : palindromeArray)
            System.out.println(s + " - is a palindrome string.");
        System.out.println("The no.of substring that are palindrome : "
                + palindromeArray.size());
    }
Output:-
Finding all palindromes in a given string : 
Given string : abcacbbbca
******** Ignoring single character as substring palindrome ********
[cac, acbbbca, cbbbc, bb, bcacb, bbb]
cac - is a palindrome string.
acbbbca - is a palindrome string.
cbbbc - is a palindrome string.
bb - is a palindrome string.
bcacb - is a palindrome string.
bbb - is a palindrome string.
The no.of substring that are palindrome : 6
Bala
  • 59
  • 2
1

I suggest building up from a base case and expanding until you have all of the palindomes.

There are two types of palindromes: even numbered and odd-numbered. I haven't figured out how to handle both in the same way so I'll break it up.

1) Add all single letters

2) With this list you have all of the starting points for your palindromes. Run each both of these for each index in the string (or 1 -> length-1 because you need at least 2 length):

findAllEvenFrom(int index){
  int i=0;
  while(true) {
    //check if index-i and index+i+1 is within string bounds

    if(str.charAt(index-i) != str.charAt(index+i+1)) 
      return; // Here we found out that this index isn't a center for palindromes of >=i size, so we can give up

    outputList.add(str.substring(index-i, index+i+1));
    i++;
  }
}
//Odd looks about the same, but with a change in the bounds.
findAllOddFrom(int index){
  int i=0;
  while(true) {
    //check if index-i and index+i+1 is within string bounds

    if(str.charAt(index-i-1) != str.charAt(index+i+1)) 
      return;

    outputList.add(str.substring(index-i-1, index+i+1));
    i++;
  }
}

I'm not sure if this helps the Big-O for your runtime, but it should be much more efficient than trying each substring. Worst case would be a string of all the same letter which may be worse than the "find every substring" plan, but with most inputs it will cut out most substrings because you can stop looking at one once you realize it's not the center of a palindrome.

Eric Barr
  • 443
  • 4
  • 8
  • You can simply reduce two possible cases to only odd-length palindromes by inserting some "special character", let's say `#`, between every two adjacent letters, ex. `abba -> a#b#b#a`. – Michał Rybak Nov 05 '13 at 23:54
  • You can treat both cases the same way if you use two end indices rather than the center (index) and a radius (i). Check out the solution I posted above. – Valentin Ruano Nov 06 '13 at 00:06
0

I tried the following code and its working well for the cases Also it handles individual characters too

Few of the cases which passed:

abaaa --> [aba, aaa, b, a, aa] 
geek  --> [g, e, ee, k] 
abbaca --> [b, c, a, abba, bb, aca] 
abaaba -->[aba, b, abaaba, a, baab, aa] 
abababa -->[aba, babab, b, a, ababa, abababa, bab] 
forgeeksskeegfor --> [f, g, e, ee, s, r, eksske, geeksskeeg, 
                      o, eeksskee, ss, k, kssk]

Code

static Set<String> set = new HashSet<String>(); 
static String DIV = "|";

public static void main(String[] args) {
    String str = "abababa";
    String ext = getExtendedString(str);

    // will check for even length palindromes
    for(int i=2; i<ext.length()-1; i+=2) {
        addPalindromes(i, 1, ext);
    }
    // will check for odd length palindromes including individual characters
    for(int i=1; i<=ext.length()-2; i+=2) {
        addPalindromes(i, 0, ext);
    }
    System.out.println(set);
}

/*
 * Generates extended string, with dividors applied
 * eg: input = abca
 * output = |a|b|c|a|
 */
static String getExtendedString(String str) {
    StringBuilder builder = new StringBuilder();
    builder.append(DIV);
    for(int i=0; i< str.length(); i++) {
        builder.append(str.charAt(i));
        builder.append(DIV);

    }
    String ext = builder.toString();
    return ext;
}

/*
 * Recursive matcher
 * If match is found for palindrome ie char[mid-offset] = char[mid+ offset]
 * Calculate further with offset+=2
 * 
 * 
 */
static void addPalindromes(int mid, int offset, String ext) {
    // boundary checks
    if(mid - offset <0 || mid + offset > ext.length()-1) {
        return;
    }
    if (ext.charAt(mid-offset) == ext.charAt(mid+offset)) {
        set.add(ext.substring(mid-offset, mid+offset+1).replace(DIV, ""));
        addPalindromes(mid, offset+2, ext);
    }
}

Hope its fine

Horrorgoogle
  • 7,858
  • 11
  • 48
  • 81
hemantvsn
  • 1,316
  • 3
  • 12
  • 24
0
public class PolindromeMyLogic {

static int polindromeCount = 0;

private static HashMap<Character, List<Integer>> findCharAndOccurance(
        char[] charArray) {
    HashMap<Character, List<Integer>> map = new HashMap<Character, List<Integer>>();
    for (int i = 0; i < charArray.length; i++) {
        char c = charArray[i];
        if (map.containsKey(c)) {
            List list = map.get(c);
            list.add(i);
        } else {
            List list = new ArrayList<Integer>();
            list.add(i);
            map.put(c, list);
        }
    }
    return map;
}

private static void countPolindromeByPositions(char[] charArray,
        HashMap<Character, List<Integer>> map) {
    map.forEach((character, list) -> {
        int n = list.size();
        if (n > 1) {
            for (int i = 0; i < n - 1; i++) {
                for (int j = i + 1; j < n; j++) {
                    if (list.get(i) + 1 == list.get(j)
                            || list.get(i) + 2 == list.get(j)) {
                        polindromeCount++;
                    } else {
                        char[] temp = new char[(list.get(j) - list.get(i))
                                + 1];
                        int jj = 0;
                        for (int ii = list.get(i); ii <= list
                                .get(j); ii++) {
                            temp[jj] = charArray[ii];
                            jj++;
                        }
                        if (isPolindrome(temp))
                            polindromeCount++;
                    }

                }
            }
        }
    });
}

private static boolean isPolindrome(char[] charArray) {
    int n = charArray.length;
    char[] temp = new char[n];
    int j = 0;
    for (int i = (n - 1); i >= 0; i--) {
        temp[j] = charArray[i];
        j++;
    }
    if (Arrays.equals(charArray, temp))
        return true;
    else
        return false;
}

public static void main(String[] args) {
    String str = "MADAM";
    char[] charArray = str.toCharArray();
    countPolindromeByPositions(charArray, findCharAndOccurance(charArray));
    System.out.println(polindromeCount);
}
}

Try out this. Its my own solution.

Bhavani
  • 1
  • 1
-1
    // Maintain an Set of palindromes so that we get distinct elements at the end
    // Add each char to set. Also treat that char as middle point and traverse through string to check equality of left and right char


static int palindrome(String str) {

    Set<String> distinctPln = new HashSet<String>();
    for (int i=0; i<str.length();i++) {
        distinctPln.add(String.valueOf(str.charAt(i)));
        for (int j=i-1, k=i+1; j>=0 && k<str.length(); j--, k++) {
            // String of lenght 2 as palindrome
            if ( (new Character(str.charAt(i))).equals(new Character(str.charAt(j)))) { 
                distinctPln.add(str.substring(j,i+1));
            }
            // String of lenght 2 as palindrome
            if ( (new Character(str.charAt(i))).equals(new Character(str.charAt(k)))) { 
                distinctPln.add(str.substring(i,k+1));
            }
            if ( (new Character(str.charAt(j))).equals(new Character(str.charAt(k)))) { 
                distinctPln.add(str.substring(j,k+1));
            } else {
                continue;
            }
        }
    }

    Iterator<String> distinctPlnItr = distinctPln.iterator();
    while ( distinctPlnItr.hasNext()) {
        System.out.print(distinctPlnItr.next()+ ",");
    }
    return distinctPln.size();

}
-1

Code is to find all distinct substrings which are palindrome. Here is the code I tried. It is working fine.

import java.util.HashSet;
import java.util.Set;

public class SubstringPalindrome {

    public static void main(String[] args) {
        String s = "abba";
        checkPalindrome(s);
}

public static int checkPalindrome(String s) {
    int L = s.length();
    int counter =0;
    long startTime = System.currentTimeMillis();
    Set<String> hs = new HashSet<String>();
    // add elements to the hash set
    System.out.println("Possible substrings: ");
    for (int i = 0; i < L; ++i) {
      for (int j = 0; j < (L - i); ++j) {
          String subs = s.substring(j, i + j + 1);
            counter++;
            System.out.println(subs);
            if(isPalindrome(subs))
                hs.add(subs);
      }
    }
    System.out.println("Total possible substrings are "+counter);
    System.out.println("Total palindromic substrings are "+hs.size());
    System.out.println("Possible palindromic substrings: "+hs.toString());
    long endTime = System.currentTimeMillis();
    System.out.println("It took " + (endTime - startTime) + " milliseconds");
    return hs.size();
}
public static boolean isPalindrome(String s) {
    if(s.length() == 0 || s.length() ==1)
        return true;
    if(s.charAt(0) ==  s.charAt(s.length()-1))
        return isPalindrome(s.substring(1, s.length()-1));
    return false;
}

}

OUTPUT:

Possible substrings: a b b a ab bb ba abb bba abba

Total possible substrings are 10

Total palindromic substrings are 4

Possible palindromic substrings: [bb, a, b, abba]

It took 1 milliseconds

zappee
  • 20,148
  • 14
  • 73
  • 129
user8778562
  • 1
  • 1
  • 1
  • 3