23

I'm posting this on behalf of a friend since I believe this is pretty interesting:

Take the string "abb". By leaving out any number of letters less than the length of the string we end up with 7 strings.

a b b ab ab bb abb

Out of these 4 are palindromes.

Similarly for the string

"hihellolookhavealookatthispalindromexxqwertyuiopasdfghjklzxcvbnmmnbvcxzlkjhgfdsapoiuytrewqxxsoundsfamiliardoesit"

(a length 112 string) 2^112 - 1 strings can be formed.

Out of these how many are palindromes??

Below there is his implementation (in C++, C is fine too though). It's pretty slow with very long words; he wants to know what's the fastest algorithm possible for this (and I'm curious too :D).

#include <iostream>
#include <cstring>

using namespace std;



void find_palindrome(const char* str, const char* max, long& count)
{
    for(const char* begin = str; begin < max; begin++) {
        count++;
        const char* end = strchr(begin + 1, *begin);
        while(end != NULL) {
            count++;
            find_palindrome(begin + 1, end, count);
            end = strchr(end + 1, *begin);
        }
    }
}


int main(int argc, char *argv[])
{
    const char* s = "hihellolookhavealookatthis";
    long count = 0;

    find_palindrome(s, strlen(s) + s, count);

    cout << count << endl;
}
Deanie
  • 2,316
  • 2
  • 19
  • 35
Andreas Bonini
  • 44,018
  • 30
  • 122
  • 156

9 Answers9

15

First of all, your friend's solution seems to have a bug since strchr can search past max. Even if you fix this, the solution is exponential in time.

For a faster solution, you can use dynamic programming to solve this in O(n^3) time. This will require O(n^2) additional memory. Note that for long strings, even 64-bit ints as I have used here will not be enough to hold the solution.

#define MAX_SIZE 1000
long long numFound[MAX_SIZE][MAX_SIZE]; //intermediate results, indexed by [startPosition][endPosition]

long long countPalindromes(const char *str) {
    int len = strlen(str);
    for (int startPos=0; startPos<=len; startPos++)
        for (int endPos=0; endPos<=len; endPos++)
            numFound[startPos][endPos] = 0;

    for (int spanSize=1; spanSize<=len; spanSize++) {
        for (int startPos=0; startPos<=len-spanSize; startPos++) {
            int endPos = startPos + spanSize;
            long long count = numFound[startPos+1][endPos];   //if str[startPos] is not in the palindrome, this will be the count
            char ch = str[startPos];

            //if str[startPos] is in the palindrome, choose a matching character for the palindrome end
            for (int searchPos=startPos; searchPos<endPos; searchPos++) {
                if (str[searchPos] == ch)
                    count += 1 + numFound[startPos+1][searchPos];
            }

            numFound[startPos][endPos] = count;
        }
    }
    return numFound[0][len];
}

Explanation:

The array numFound[startPos][endPos] will hold the number of palindromes contained in the substring with indexes startPos to endPos.

We go over all pairs of indexes (startPos, endPos), starting from short spans and moving to longer ones. For each such pair, there are two options:

  1. The character at str[startPos] is not in the palindrome. In that case, there are numFound[startPos+1][endPos] possible palindromes - a number that we have calculated already.

  2. character at str[startPos] is in the palindrome (at its beginning). We scan through the string to find a matching character to put at the end of the palindrome. For each such character, we use the already-calculated results in numFound to find number of possibilities for the inner palindrome.

EDIT:

  • Clarification: when I say "number of palindromes contained in a string", this includes non-contiguous substrings. For example, the palindrome "aba" is contained in "abca".

  • It's possible to reduce memory usage to O(n) by taking advantage of the fact that calculation of numFound[startPos][x] only requires knowledge of numFound[startPos+1][y] for all y. I won't do this here since it complicates the code a bit.

  • Pregenerating lists of indices containing each letter can make the inner loop faster, but it will still be O(n^3) overall.

interjay
  • 107,303
  • 21
  • 270
  • 254
  • @Pavel Shved: Can you clarify what you mean? My answer gives the same results as the original code, once the bug I mentioned is fixed. – interjay Jan 09 '10 at 17:38
  • 1
    I am afraid you are slightly wrong in your use of subindexes. Personally I would say that "leaving out any number of letters" means that from "abc" one would derive "a b c ab ac bc abc". The actual example is a bit shaky on this (using "abb") but you will notice that in the derived list "ab" appears twice whereas if the strings were contiguous you would only derive "a b b ab bb abb". – Matthieu M. Jan 09 '10 at 18:57
  • 2
    @Matthieu M.: My answer does not look only at contiguous substrings - it does exactly what the question asks. For example, using the string "aaa" will give a result of 7 and not 6 as it would if it only counted contiguous substrings. If you think otherwise, please provide an example string where my answer gives the wrong result. – interjay Jan 09 '10 at 19:15
  • My apology, I was misguided by the use of contiguous spans. – Matthieu M. Jan 09 '10 at 20:53
6

I have a way can do it in O(N^2) time and O(1) space, however I think there must be other better ways.

the basic idea was the long palindrome must contain small palindromes, so we only search for the minimal match, which means two kinds of situation: "aa", "aba". If we found either , then expand to see if it's a part of a long palindrome.

    int count_palindromic_slices(const string &S) {
        int count = 0;

        for (int position=0; position<S.length(); position++) {
            int offset = 0;

            // Check the "aa" situation
            while((position-offset>=0) && (position+offset+1)<S.length() && (S.at(position-offset))==(S.at(position+offset+1))) {
                count ++;
                offset ++;
            }

            offset = 1;  // reset it for the odd length checking
            // Check the string for "aba" situation
            while((position-offset>=0) && position+offset<S.length() && (S.at(position-offset))==(S.at(position+offset))) {
                count ++;
                offset ++;
            }
        }
        return count;
    }

June 14th, 2012 After some investigation, I believe this is the best way to do it. faster than the accepted answer.

StanleyZ
  • 598
  • 6
  • 22
  • This answer is wrong, so its relative speed is irrelevant. `count_palindromic_spaces("abb")` returns 1 when the question says it should return 4. (The answer for a string of length *n* is always at least *n*.) It returns 3 for "aaa" when it should return 7. There are two problems with the code. First, it requires palindromes have a minimum length of 2 when it should be 1. Second, it's only checking contiguous strings of characters when it should be checking all 2^n-1 character selections. – Rob Kennedy Aug 09 '13 at 14:56
  • Hi @RobKennedy, Thank you for point out the error of my code. For the 1st one, I understand it and I agree with you, in this post every single character should count. it could be a easy fix by count++ in every for section. For the 2nd one, I'm not sure I got it. could you explain more? e.g. why "aaa" should be 7? I thought it should be 6(a,a,a,aa,aa,aaa) even in original post attention? – StanleyZ Aug 14 '13 at 15:13
  • 1
    There are seven different selections of characters from a three-character string (because 7 = 2^3-1). It's hard to illustrate when all the letters are the same, so let's look at "abc" instead. Here are the seven strings: abc, ab, ac, bc, a, b, c. The one your algorithm overlooks is ac. When all the letters are the same, those seven strings are obviously all palindromes, so the result from `count_palindromic_slices("aaa")` should be 7. – Rob Kennedy Aug 14 '13 at 15:36
2

Is there any mileage in making an initial traversal and building an index of all occurances of each character.

 h = { 0, 2, 27}
 i = { 1, 30 }
 etc.

Now working from the left, h, only possible palidromes are at 3 and 17, does char[0 + 1] == char [3 -1] etc. got a palindrome. does char [0+1] == char [27 -1] no, No further analysis of char[0] needed.

Move on to char[1], only need to example char[30 -1] and inwards.

Then can probably get smart, when you've identified a palindrome running from position x->y, all inner subsets are known palindromes, hence we've dealt with some items, can eliminate those cases from later examination.

djna
  • 54,992
  • 14
  • 74
  • 117
2

My solution using O(n) memory and O(n^2) time, where n is the string length:

palindrome.c:

#include <stdio.h>
#include <string.h>

typedef unsigned long long ull;

ull countPalindromesHelper (const char* str, const size_t len, const size_t begin, const size_t end, const ull count) {
  if (begin <= 0 || end >= len) {
    return count;
  }
  const char pred = str [begin - 1];
  const char succ = str [end];
  if (pred == succ) {
    const ull newCount = count == 0 ? 1 : count * 2;
    return countPalindromesHelper (str, len, begin - 1, end + 1, newCount);
  }
  return count;
}

ull countPalindromes (const char* str) {
  ull count = 0;
  size_t len = strlen (str);
  size_t i;
  for (i = 0; i < len; ++i) {
    count += countPalindromesHelper (str, len, i, i, 0);  // even length palindromes
    count += countPalindromesHelper (str, len, i, i + 1, 1); // odd length palindromes
  }
  return count;
}

int main (int argc, char* argv[]) {
 if (argc < 2) {
  return 0;
 }
 const char* str = argv [1];
 ull count = countPalindromes (str);
 printf ("%llu\n", count);
 return 0;
}

Usage:

$ gcc palindrome.c -o palindrome
$ ./palindrome myteststring

EDIT: I misread the problem as the contiguous substring version of the problem. Now given that one wants to find the palindrome count for the non-contiguous version, I strongly suspect that one could just use a math equation to solve it given the number of distinct characters and their respective character counts.

Thomas Eding
  • 35,312
  • 13
  • 75
  • 106
  • As you said this only finds contiguous substrings. I doubt you can find a math equation to go from this to the correct solution as you said: For example, the strings "abcdabcd" and "abcdabdc" both give 8 in your solution and both have the same character counts. However the correct solution is different for both (24 and 27 respectively). – interjay Jan 09 '10 at 20:02
  • Oh, I see. I mistook the meaning of non-contiguous as being completely free with respect to reordering. IOW, any subpermutation would be game. – Thomas Eding Jan 09 '10 at 20:14
1

Hmmmmm, I think I would count up like this:

Each character is a palindrome on it's own (minus repeated characters).
Each pair of the same character.
Each pair of the same character, with all palindromes sandwiched in the middle that can be made from the string between repeats.
Apply recursively.

Which seems to be what you're doing, although I'm not sure you don't double-count the edge cases with repeated characters.

So, basically, I can't think of a better way.

EDIT:
Thinking some more, It can be improved with caching, because you sometimes count the palindromes in the same sub-string more than once. So, I suppose this demonstrates that there is definitely a better way.

James
  • 24,676
  • 13
  • 84
  • 130
  • But strchr() is expensive. Working from smaller palindromes outward, it's necessary to compare only the first and last characters, stopping at the first mismatch. – Adam Liss Jan 09 '10 at 16:54
  • I don't know: O(n) isn't the most expensive operation in the world. I admit there is probably a better solution, but I'm not sure it comes from either a top-down or bottom up approach. – James Jan 09 '10 at 17:01
1

Here is a program for finding all the possible palindromes in a string written in both Java and C++.

bragboy
  • 34,892
  • 30
  • 114
  • 171
  • Those are indeed programs for finding palindromes, but they're not programs for finding all the palindromes *this* question asks about. – Rob Kennedy Aug 09 '13 at 14:57
0
int main()
 {
    string palindrome;

    cout << "Enter a String to check if it is a Palindrome";

    cin >> palindrome;

    int length = palindrome.length();

    cout << "the length of the string is " << length << endl;

    int end = length - 1;
    int start = 0;
    int check=1;

    while (end >= start) {
        if (palindrome[start] != palindrome[end]) {
            cout << "The string is not a palindrome";
            check=0;
            break;
        }
        else
        {
            start++;
            end--;

        }

    }
    if(check)
    cout << "The string is a Palindrome" << endl;

}
Angelo
  • 1
  • 1
    **1.** You don't have to interate over the whole word (with `while (end >= start)`) but only through the half of it, since you already compared the second half... **2.** Your post don't answer the question at all... – Kyle_the_hacker Aug 09 '13 at 14:33
0
public String[] findPalindromes(String source) {

    Set<String> palindromes = new HashSet<String>();        
    int count = 0;
    for(int i=0; i<source.length()-1; i++) {            
        for(int j= i+1; j<source.length(); j++) {               
            String palindromeCandidate = new String(source.substring(i, j+1));
            if(isPalindrome(palindromeCandidate)) {
                palindromes.add(palindromeCandidate);                   
            }
        }
    }

    return palindromes.toArray(new String[palindromes.size()]);     
}

private boolean isPalindrome(String source) {

    int i =0;
    int k = source.length()-1;      
    for(i=0; i<source.length()/2; i++) {            
        if(source.charAt(i) != source.charAt(k)) {
            return false;
        }
        k--;
    }       
    return true;
}
Abidi
  • 7,846
  • 14
  • 43
  • 65
-2

I am not sure but you might try whit fourier. This problem remined me on this: O(nlogn) Algorithm - Find three evenly spaced ones within binary string

Just my 2cents

Community
  • 1
  • 1
Luka Rahne
  • 10,336
  • 3
  • 34
  • 56