计算一个字符串中有多少回文

时间:2019-06-19 23:21:59

标签: java string algorithm palindrome

我想使用给定字符串中的子字符串找出所有可能的回文。

Example: input = abbcbbd.
Possible palindromes are a,b,b,c,b,b,d,bb,bcb, bbcbb,bb

这是我实现的逻辑:

public int palindromeCount(String input) {
    int size = input.length();// all single characters in string are treated as palindromes.
    int count = size;
    for(int i=0; i<size; i++) {
        for(int j=i+2; j<=size; j++) {
            String value = input.substring(i, j);
            String reverse = new StringBuilder(value).reverse().toString();
            if(value.equals(reverse)) {
                count++;
            }
        }
    }
    return count;
}

这里的时间复杂度更高,如何改善这种逻辑的性能?

2 个答案:

答案 0 :(得分:6)

在优化此算法时,您可以考虑以下几点:

  1. 什么是回文?回文是一个对称字符串,这意味着它必须具有中心枢轴!枢轴可能是以下之一:

    • 字母,例如“ a b a”或

    • 两个字母之间的位置,例如字母“ aa”之间的位置

这意味着总共有2 n -1个可能的枢轴。

然后,您可以从每个枢轴向外搜索。这是一个示例:

  • 示例字符串:“ abcba”
  • 首先,让我们以“ c”为中心:
    • ab c ba→c是回文,然后将搜索范围扩大1。
    • a bcb a→bcb是回文,然后在每一侧将搜索范围扩大1。
    • abcba →abcba是回文,所以我们知道字符串中至少有3个回文。
  • 继续进行所有工作。

这种方法的运行时复杂度为 O n 2 )。

答案 1 :(得分:5)

如果您愿意淘汰一些重量级的数据结构,则可以在时间O(n)进行,尽管我承认这并不是特别容易编写的东西。 :-)

我们将需要两个工具来解决此问题。

工具一:广义后缀树。广义后缀树是一种数据结构,直观地讲,它是一个Trie,包含两个字符串S和T的所有后缀,但以节省空间的方式表示

工具二:最低公共祖先查询。最低公共祖先查询结构(或LCA查询)是围绕特定树构建的数据结构。它旨在有效地回答以下形式的查询:“给定树中的两个节点,它们的最低共同祖先是什么?”

重要的是,可以在时间O(m)中建立两个长度为m的字符串的通用后缀树,并且可以在时间O(m)中建立LCA查询,以使所有查询都花费时间O(1)。这些不是明显的运行时。最初发现它们时,这里需要的算法和数据结构是可发布的结果。

假设我们具有这两种结构,我们可以构建第三个数据结构,这将是我们用来获得最终算法的方法:

工具三:最长的公共扩展查询。最长的公共扩展查询数据结构(或LCE查询)是围绕两个字符串S和T构建的数据结构。它支持以下形式的查询:给定在字符串S中的索引i和在字符串T中的索引j,从S中的索引i和T中的索引j开始出现的最长字符串的长度是多少?

以下面的两个字符串为例:

S: OFFENSE
   0123456

T: NONSENSE
   01234567

如果我们从字符串S的索引3和字符串T的索引4开始进行LCE查询,答案将是字符串ENSE。另一方面,如果我们从字符串S的索引4和字符串T的索引0开始进行LCE查询,则会得到字符串N。

(更严格地说,LCE查询结构实际上不会返回在两个地方都可以找到的实际字符串,而是它的长度。)

可以为时间为O(m)的长度为m的一对字符串S和T建立LCE数据结构,以便每个查询花费时间O(1)。这样做的技术包括为两个字符串构建通用后缀树,然后在顶部构建LCA数据结构。关键的见解是,在后缀树中,从字符串S的后缀i和字符串T的后缀j的最小公祖给出了从字符串S的第i个位置和字符串T的第j个位置开始的LCE。

LCE结构对于此问题非常有用。要了解原因,让我们以示例字符串abbcbbd为例。现在,考虑该字符串及其反向,如下所示:

 S: abbcbbd
    0123456

 T: dbbcbba
    0123456

字符串中的每个回文都采用以下两种形式之一。首先,它可以是奇长回文。像这样的回文中心具有一些中心字符c,以及从该中心向前和向后延伸的一些“半径”。例如,字符串bbcbb是一个奇数回文,中心为c,半径为bb

通过使用LCE查询,我们可以算出一个字符串中有多少个奇数回文。特别是,在字符串及其反向上构建LCE查询结构。然后,对于原始字符串中的每个位置,要求原始字符串中该位置的LCE及其在镜像字符串中的对应位置。这将为您提供以该点为中心的最长的奇长回文。 (更具体地说,它将为您提供半径的长度加一,因为角色本身将始终在这两个点上匹配)。一旦我们知道以字符串中该位置为中心的最长奇数回文,就可以计算以字符串中该位置为中心的奇数回文的 number :将等同于我们可以采用更长回文并通过切断前后字符来缩短回文的所有方法。

考虑到这一点,我们可以对字符串中的所有奇数回文数进行计数,如下所示:

for i from 0 to length(S) - 1:
    total += LCE(i, length(S) - 1 - i)

另一类回文体是等长回文体,它们没有中心,而是由两个相等的半径组成。我们也可以使用LCE查询找到这些内容,除了要查看原始字符串中的位置i和对应位置,而不是查看某个位置i及其在反向字符串中的对应位置。在颠倒的字符串中为i-1编制索引。可以在这里完成:

for i from 1 to length(S) - 1:
    total += LCE(i, length(S) - i)

总体而言,此解决方案

  • 根据原始字符串和反向字符串构造LCE查询结构。反转字符串需要时间O(m),而构建LCE查询结构则需要时间O(m)。此步骤的总时间: O(m)
  • 对LCE结构进行总共2m-1个查询。每个查询花费时间O(1)。此步骤的总时间: O(m)

我相当确定可以在不使用此类重量级工具的情况下实现此运行时,但这至少表明存在线性时间解决方案。

希望这会有所帮助!