Найти все подстроки, являющиеся палиндромами

Если на входе "АББА", то можно палиндромы-это, б, б, а, ВВ, АВВА.
Я понимаю, что определить, является ли строка палиндромом, легко. Было бы так:

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;  
}

но каков эффективный способ поиска подстрок палиндрома?

8 ответов


это можно сделать в O(n), используя Манакера это.

основная идея-это сочетание динамического программирования и (как уже говорили другие) вычисляя максимальную длину палиндрома с центром в заданной букве.


то, что мы действительно хотим рассчитать, это радиус самого длинного палиндрома, не длины. The радиус просто length/2 или (length - 1)/2 (по нечетной длины палиндромы.)

после вычисления радиуса палиндром pr на данной позиции i мы используем уже вычисленные указатели найти палиндромы в диапазоне [i - pr ; i]. Это позволяет нам (потому что палиндромы, ну, палиндромы) пропустить дальнейшее вычисление radiuses на выбор [i ; i + pr].

пока мы ищем в ряде [i - pr ; i], есть четыре основных случая для каждой позиции i - k (где k находится в 1,2,... pr):

  • нет палиндром (radius = 0) at i - k
    (это означает radius = 0 at i + k тоже)
  • внутренний палиндром, что означает, что он подходит в диапазоне
    (это средство radius at i + k по состоянию на i - k)
  • внешний палиндром, что означает, что он не вписывается в диапазон
    (это означает radius at i + k is спилил чтобы вписаться в диапазон, i.e потому что i + k + radius > i + pr уменьшить radius до pr - k)
  • Липки палиндром, который значит i + k + radius = i + pr
    (в этом случае нам нужно искать потенциально больший радиус при i + k)

полное, подробное объяснение было бы довольно длинным. Как насчет некоторых образцов кода? :)

я нашел c++ реализацию этого алгоритма польским учителем, мгром Ежи Валашеком.
Я перевел комментарии на английский язык, добавил некоторые другие комментарии и немного упростил его, чтобы легче было поймать основной часть.
посмотреть здесь.


Примечание: в случае проблем с пониманием, почему это O(n), попробуйте посмотреть сюда:
после нахождения радиус (назовем его r) в какой-то позиции нам нужно перебрать r элементы, но в результате мы можем пропустить расчета r элементы вперед. Таким образом, общее число итерированных элементов остается прежним.


возможно, вы могли бы перебирать потенциальные средние символы (палиндромы нечетной длины) и средние точки между символами (палиндромы четной длины) и расширять каждый, пока вы не сможете получить дальше (следующие левые и правые символы не совпадают).

это сэкономит много вычислений, когда в строке не будет много палидромов. В таком случае стоимость будет O(n) для разреженных палидромовых строк.

для плотных входов палиндрома это будет O (n^2) как каждый позиция не может быть расширена больше, чем длина массива / 2. Очевидно, что это еще меньше к концам массива.

  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++;
      }
  }

Итак, каждая отдельная буква уже является палиндромом-поэтому у вас уже есть N + 1 палиндромов, где N-количество отдельных букв (плюс пустая строка). Вы можете сделать это за один прогон-O (N).

теперь, для нетривиальных палиндромов, вы можете проверить каждую точку вашей строки, чтобы быть центром потенциального палиндрома-расти в обоих направлениях-что-то, что Валентин Руано предложил.
Это решение будет принимать O (n^2), так как каждый тест равен O (N) и числу из возможных "центров" также O (N) -center - Это либо буква, либо пробел между двумя буквами, как и в решении Валентина.

обратите внимание, что существует также O(N) решение вашей проблемы, основанное на Манакера это алгоритм (статья описывает "самый длинный палиндром", но алгоритм может быть использован для подсчета всех из них)


Я просто придумал свою собственную логику, которая помогает решить эту проблему. Удачи в кодировании.. :-)

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

Я предлагаю строить из базового случая и расширять, пока у вас не будут все палиндомы.

есть два типа палиндромов: четные и нечетные. Я не понял, как обращаться с обоими одинаково, поэтому я его разорву.

1) Добавить все отдельные буквы

2) в этом списке у вас есть все отправные точки для ваших палиндромов. Запустите каждый из них для каждого индекса в строке (или 1 - > length-1, потому что вам нужно не менее 2 длина):

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++;
  }
}

Я не уверен, что это помогает Big-O для вашей среды выполнения, но это должно быть намного эффективнее, чем пытаться каждую подстроку. В худшем случае это будет строка из одной и той же буквы, которая может быть хуже, чем план "найти каждую подстроку", но с большинством входов она вырезает большинство подстрок, потому что вы можете перестать смотреть на одну, как только поймете, что это не центр палиндрома.


    // 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();

}

я попробовал следующий код, и работает хорошо для дела Также он обрабатывает отдельные символы тоже

несколько случаев, которые прошли:

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]

код

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);
    }
}

надеюсь, что его штраф в размере


код должен найти все различные подстроки, которые являются палиндромом. Вот код, который я пробовал. Он работает нормально.

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;
}

}

выход:

возможные подстроки: ля си си ля ав bb бакалавр искусств АББ bba АББА!--2-->

всего возможных подстрок 10

всего палиндромных подстрок 4

возможные палиндромные подстроки: [bb, a, b, abba]

прошло 1 мс