我试图在字符串中找到最长的回文。蛮力解决方案需要O(n ^ 3)时间。我读到有一个使用后缀树的线性时间算法。我熟悉后缀树,很舒服地建造它们。如何使用构建的后缀树找到最长的回文。
答案 0 :(得分:29)
线性解决方案可以通过这种方式找到::
<强> Prequisities:强>
(1)。您必须知道如何在O(N)或O(NlogN)时间内构造后缀数组。
(2)。您必须知道如何找到标准LCP阵列即。相邻后缀i和i-1之间的LCP
即。对于(i> 0),LCP [i] = LCP(排序数组中的后缀i,排序数组中的后缀i-1)。
让 S 为原始字符串, S'与原始字符串相反。 让我们以S =“香蕉”为例。 然后它的反向字符串S'= ananab。
第1步:连接 S +#+ S'以获取String 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
请注意,后缀数组是一个整数数组,以字典顺序给出字符串后缀的起始位置。因此,保存起始位置索引的数组是后缀数组。
那是 SuffixArray [] = {6,5,11,3,9,1,7,12,0,4,10,2,8};
第3步:由于您已设法构建后缀数组,现在找到相邻后缀之间的最长公共前缀。
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不得包含#“OR “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
答案 1 :(得分:25)
我相信你需要这样做:
让 y 1 y 2 ... y n 是你的字符串(其中 y i 是字母)。
创建 S f = y 1 <的通用后缀树em> y 2 ... y n $ 和 S r = y n y n - 1 ... y 1 #(反转字母并为 S f ($)和 S r 选择不同的结束字符(#))...其中 S f 代表“String,Forward”和 S r < / sub> 代表“String,Reverse”。
对于 S f 中的每个后缀 i ,找到后缀为 n - i + 1 <的最低共同祖先 in S r 。
从根到最低共同祖先是回文的是什么,因为现在最低的共同祖先代表这两个后缀的最长共同前缀。回想一下:
(1)后缀的前缀是子串。
(2)回文是与其反向相同的字符串。
(3)因此,字符串中包含的最长的回文恰好是该字符串的最长公共子字符串及其反向。
(4)因此,字符串中包含的最长的回文正好是字符串及其反向之间所有后缀对的最长公共前缀。这就是我们在这里所做的。
示例强>
我们来一词 banana 。
S f = banana $
S r = ananab#
下面是 S f 和 S r 的通用后缀树,其中的数字在最后每个路径的索引是相应后缀的索引。有一个小错误,Blue_4父母的所有3个分支共同的 a 应位于其进入边缘,旁边是 n :
树中最低的内部节点是此字符串的最长公共子字符串,反之亦然。查看树中的所有内部节点,您将找到最长的回文。
在Green_0和Blue_1之间找到最长的回文(即香蕉和 anana )并且是 anana
修改强>
我刚刚发现this paper回答了这个问题。
答案 2 :(得分:5)
迟了几年......
假设s
是原始字符串,而r
是s
可逆的。我们还假设我们使用ST
完全构建了后缀树s
。
我们的下一步是针对r
检查ST
的所有后缀。对于r
的每个新后缀,我们将保留我们与树中预先存在的后缀成功匹配的第一个k
个字符的计数(即s
的一个后缀)。
例如,假设我们匹配来自r
的后缀“RAT”,而s
包含一些以“RA”开头的后缀,但没有匹配“RAT”。当我们最终不得不放弃对最终字符“T”的希望时,k
将等于2。我们将r
后缀的前两个字符与s
后缀的前两个字符进行匹配。我们将此节点称为n
。
现在,我们怎么知道我们什么时候找到了回文? 通过检查n
下的所有叶节点。
在传统的后缀树中,每个后缀的起始索引存储在该后缀分支的叶节点处。在上面的示例中,s
可能包含一堆以“RA”开头的后缀,每个后缀都从{{1}的叶节点后代中的一个索引处开始。 }。
让我们使用这些指数。
如果我们将n
个子串中的k
个字符与R
中的k
个字符进行匹配,这意味着什么?嗯,这只是意味着我们发现了一些字符串被逆转但是,如果子串在ST
开始的位置等于R
加S
中的匹配子串,这意味着什么?是的,这意味着k
读取的内容与s[i] through s[i+k]
相同!因此,定义我们找到了一个大小为s[i+k] through s[i]
的回文。
现在,您所要做的就是在目前为止发现的最长的回文上留下一个标签,并在功能结束时将其返回。
答案 3 :(得分:1)
来自Skiena - The Algorithm Design Manual
在S中找到最长的回文[使用后缀树] - 回文是一个字符串,如果字符顺序颠倒则读取相同的字符串,例如女士。要在字符串S中找到最长的回文结构,请构建一个后缀树,其中包含S的所有后缀和S的反转,每个叶子由其起始位置标识。回文结构由该树中的任何节点定义,该节点具有来自相同位置的前向和后向子节点。
答案 4 :(得分:-2)
DP解决方案:
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;
}