我试图在一个字符串中找到最长的回文串。暴力解法需要O(n^3)的时间。我读到了用后缀树可以实现线性时间算法。我熟悉后缀树,也能够很好的构建它们。那么如何使用构建好的后缀树来找到最长的回文串。
我试图在一个字符串中找到最长的回文串。暴力解法需要O(n^3)的时间。我读到了用后缀树可以实现线性时间算法。我熟悉后缀树,也能够很好的构建它们。那么如何使用构建好的后缀树来找到最长的回文串。
Linear解法如下:
前提条件:
(1).你必须知道如何在O(N)或O(NlogN)时间内构建后缀数组
(2).你必须知道如何找到标准的LCP数组即相邻后缀i和i-1之间的LCP
即 LCP [i]=LCP(suffix i在已排序的数组中, suffix i-1在已排序的数组中),其中(i>0)。
假设原始字符串为S,反转后的字符串为S'。 以S="banana"为例,那么它的反转字符串是S'=ananab。
步骤1: 将S + # + S'连接起来得到字符串Str,其中#是原始字符串中不存在的字母。
Concatenated String Str=S+#+S'
Str="banana#ananab"
步骤2:现在构建字符串Str的后缀数组。
在这个例子中,后缀数组是:
Suffix Number Index Sorted Suffix
0 6 #ananab
1 5 a#ananab
2 11 ab
3 3 ana#ananab
4 9 anab
5 1 anana#ananab
6 7 ananab
7 12 b
8 0 banana#ananab
9 4 na#ananab
10 10 nab
11 2 nana#ananab
12 8 nanab
请注意,后缀数组是一个由整数组成的数组,按字典顺序给出字符串后缀的起始位置。因此,保存起始位置索引的数组就是后缀数组。LCP between #ananab a#ananab is :=0
LCP between a#ananab ab is :=1
LCP between ab ana#ananab is :=1
LCP between ana#ananab anab is :=3
LCP between anab anana#ananab is :=3
LCP between anana#ananab ananab is :=5
LCP between ananab b is :=0
LCP between b banana#ananab is :=1
LCP between banana#ananab na#ananab is :=0
LCP between na#ananab nab is :=2
LCP between nab nana#ananab is :=2
LCP between nana#ananab nanab is :=4
因此,LCP数组为LCP={0,0,1,1,3,3,5,0,1,0,2,2,4}。
其中 LCP[i]=后缀i和(i-1)的最长公共前缀长度。(对于 i>0)
步骤4:
现在您已经构建了一个LCP数组,请使用以下逻辑。
Let the length of the Longest Palindrome ,longestlength:=0 (Initially)
Let Position:=0.
for(int i=1;i<Len;++i)
{
//Note that Len=Length of Original String +"#"+ Reverse String
if((LCP[i]>longestlength))
{
//Note Actual Len=Length of original Input string .
if((suffixArray[i-1]<actuallen && suffixArray[i]>actuallen)||(suffixArray[i]<actuallen && suffixArray[i-1]>actuallen))
{
//print :Calculating Longest Prefixes b/w suffixArray[i-1] AND suffixArray[i]
longestlength=LCP[i];
//print The Longest Prefix b/w them is ..
//print The Length is :longestlength:=LCP[i];
Position=suffixArray[i];
}
}
}
So the length of Longest Palindrome :=longestlength;
and the longest palindrome is:=Str[position,position+longestlength-1];
执行示例:
actuallen=Length of banana:=6
Len=Length of "banana#ananab" :=13.
Calculating Longest Prefixes b/w a#ananab AND ab
The Longest Prefix b/w them is :a
The Length is :longestlength:= 1
Position:= 11
Calculating Longest Prefixes b/w ana#ananab AND anab
The Longest Prefix b/w them is :ana
The Length is :longestlength:= 3
Position:=9
Calculating Longest Prefixes b/w anana#ananab AND ananab
The Longest Prefix b/w them is :anana
The Length is :longestlength:= 5
Position:= 7
So Answer =5.
And the Longest Palindrome is :=Str[7,7+5-1]=anana
只需做个笔记::
第4步中的if条件基本上是指,在每次迭代(i)中,如果我取后缀s1(i)和s2(i-1),那么“s1必须包含#且s2不能包含#”或者“s2必须包含#且s1不能包含#”。
|(1:BANANA#ANANAB)|leaf
tree:|
| | | |(7:#ANANAB)|leaf
| | |(5:NA)|
| | | |(13:B)|leaf
| |(3:NA)|
| | |(7:#ANANAB)|leaf
| | |
| | |(13:B)|leaf
|(2:A)|
| |(7:#ANANAB)|leaf
| |
| |(13:B)|leaf
|
| | |(7:#ANANAB)|leaf
| |(5:NA)|
| | |(13:B)|leaf
|(3:NA)|
| |(7:#ANANAB)|leaf
| |
| |(13:B)|leaf
|
|(7:#ANANAB)|leaf
我认为你需要按照以下步骤进行:
设y1y2...yn是你的字符串(其中yi代表字母)。
创建Sf=y1y2...yn$和Sr=ynyn-1...y1#的广义后缀树(将字母反转并选择不同的结束字符用于Sf($)和Sr(#),其中Sf表示“正向字符串”,Sr表示“反向字符串”)。
对于Sf中的每个后缀i,找到它与Sr中后缀n-i+1的最近公共祖先。
从根结点到最近公共祖先的路径是一个回文串,因为现在最近公共祖先代表这两个后缀的最长公共前缀。请注意:
(1) 后缀的前缀是子串。
(2) 回文串是与其反转相同的字符串。
(3) 因此,字符串中包含的最长回文串正好是该字符串及其反转的最长公共子串。
(4) 因此,一个字符串中最长的回文子串恰好是该字符串与其反转后所有后缀对之间的最长公共前缀。这就是我们在这里做的事情。
示例
让我们来看看单词banana。
Sf = banana$
Sr = ananab#
下面是Sf和Sr的广义后缀树,其中每个路径末尾的数字是对应后缀的索引。Blue_4的父节点所有3个分支共同拥有的a应该在其进入边旁边的n上,这里有一个小错误:
树中最低的内部节点是该字符串及其反转的最长公共子串。因此,查看树中的所有内部节点,您将找到最长的回文子串。
最长的回文子串位于Green_0和Blue_1之间(即banana 和 anana),为anana
编辑
我刚刚发现了这篇文章,它回答了这个问题。
使用后缀树查找S中的最长回文串 - 回文串是一种字符串,如果字符顺序被反转,则读起来相同,例如madam。要在字符串S中查找最长回文串,请构建一个包含S的所有后缀和S的反转的单个后缀树,并将每个叶子标识为其起始位置。此树中任何具有正向和反向孩子的节点都定义为回文。
动态规划解决方案:
int longestPalin(char *str)
{
n = strlen(str);
bool table[n][n]l
memset(table, 0, sizeof(table));
int start = 0;
for(int i=0; i<n; ++i)
table[i][i] = true;
int maxlen = 1;
for(int i=0; i<n-1; ++i)
{
if(str[i] == str[i+1])
{
table[i][i] = true;
start = i;
maxlen = 2;
}
}
for(int k=3; k<=n; ++k)
{
for(int i=0; i<n-k+1; ++i)
{
int j = n+k-1;
if(str[i] == str[j] && table[i+1][j-1])
{
table[i][j] = true;
if(k > maxlen)
{
start = i;
maxlen = k;
}
}
}
}
print(str, start, start+maxlen-1);
return maxlen;
}