1.问题引入
最长回文子串:即在给定的串中找到一个子串,该子串是回文串且长度最长。回文串即对称串。
举例:s = “abadaba”,回文子串有"aba",“abadaba”,而最大的即是s本身
2.常规方法
<1>暴力算法(O(N3))
暴力枚举每一个子串,判断是否为回文串。若是,记录并更新长度;若不是,则跳过。
#include <iostream>
#include <string>
#include <ctime>
using namespace std;
bool judge(string s)
{
int i = 0,j = s.length() - 1;
while(i < j &&s[i++] == s[j--])
{
}
return i >= j;
}
int main()
{
string s = "";
cin >> s;
int len = s.length();
int flag = 0;
int fir = 0;
int plen = 1;
//枚举s的每个子串
for(int l = len; l ;--l)
{
//枚举字串长度
for(int bg = 0;bg <= len - l; bg++)
{
if(judge(s.substr(bg,l)))
{
flag = 1;
plen = l;
fir = bg;
break;
}
}
if(flag) break;
}
if(plen)
{
cout <<s.substr(fir,plen);
}
return 0;
}在这里插入代码片
<2>.中心扩展(O(N2))
中心扩展(有些地方也叫dp?):简言之,对于串的每一个位置为中心,向左右两边扩展(判断两端是否相等),记录并更新长度。(这里带来了个问题,中心可能会是两个字符间的位置)
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
int LongestPalindromeLenth(string s, int &plen, int &fir)
{
int ret = 1;
// 单个中心扩展
for (int i = 1; i < s.length() - 1; i++)
{
int lp = i - 1, rp = i + 1;
while (lp >= 0 && rp <= s.length() - 1 && s[lp] == s[rp])
{
lp--, rp++;
}
if (rp - lp - 1 > ret)
{
fir = lp + 1;
ret = plen = rp - lp - 1;
}
}
//两个中心扩展
for (int i = 0; i < s.length() - 1; i++)
{
if (s[i] != s[i + 1])
continue;
int lp = i - 1, rp = i + 2;
while (lp >= 0 && rp <= s.length() - 1 && s[lp] == s[rp])
{
lp--, rp++;
}
if (rp - lp - 1 > ret)
{
fir = lp + 1;
ret = plen = rp - lp - 1;
}
}
return ret;
}
int main()
{
string s;
cin >> s;
int fir = 0, plen = 1;
LongestPalindromeLenth(s, plen, fir);
cout << s.substr(fir, plen);
return 0;
}
3.MANACHER(O(N))
引言:macher是如何做到线性复杂度处理最长回文字串的呢?最重要的是利用了回文串的特点即左右对称。
在暴力算法中我们没有考虑到对称中心这个因素,在中心对称算法中则是利用了这一点;而在manacher算法中,由于中心扩展算法中并未对各个对称中心间的关系进行优化(即在前一个对称中心扩展的整个回文区间可能会包含后一个中心扩展的回文区间),正是利用这一点manacher进行了优化。
<1>LPS(LongestPalindromeString)数组
que:如何统一奇偶对称中心?
ans: 在每个字符左右加一个特殊字符。
ex:
|string | | a | |b | | a| | b| |a | |b | | a|
|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|
|lps|
∣
a
∣
|a |
∣a∣ |b |
∣
a
∣
|a|
∣a∣|b|
∣
a
∣
|a|
∣a∣|b|
∣
a
∗
∗
L
P
S
[
p
o
s
]
=
d
含义
∗
∗
:
在当前位置能向左右扩展的最大位数
∣
L
P
S
∣
|a **LPS[pos] = d含义** : 在当前位置能向左右扩展的最大位数 |LPS|
∣a∗∗LPS[pos]=d含义∗∗:在当前位置能向左右扩展的最大位数∣LPS∣ | a | $ |b | $ | a |$ | b |$ |a |$ |b |
∣
a
∣
|a|
∣a∣|
|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|–|
|pos| 0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|
|exlenth| 0|1|0|3|0|5|0|7|0|5|0|3|0|1|0
center:当前对称中心
ileft : 关于对称中心对称的左对称中心
i : 需要计算的对称中心
lb :向左扩展的边界
rb :向右扩展的边界
que0: 什么时候可以从直接求得的 LPS[pre] 求出当前的 LPS[i] 的值?
ans0: 通过对称的点扩展长度扩展并未达到右边界。
即 i + LPS[il] < rb
既然存在i + LPS[il] < rb,则必存在i + LPS[il] >= rb的,此时必有LPS[i]>=rb - i;此时需要看能否继续扩展,所以剩下的就是中心扩展算法
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define maxn 2000000 + 100
int LPS[2*maxn];
char s[maxn];
char exs[maxn*2];
int manacher(char s[])
{
int len = strlen(s);
exs[0] = '|';
for(int i = 0;i < len;i++)
{
exs[2*i + 1] = s[i];
exs[2*i + 2] = '|';
}
//字符串处理
int rb = 1,lb = 1,center = 1,il = 0;
LPS[0] = LPS[2*len] = 0;//首尾特殊处理
int ret = 1;
for(int i = 1;i < 2*len;i++)
{
il = 2*center - i;
LPS[i] = min(LPS[il],rb - i);
//取最小的扩展长度
int clb = i - LPS[i];
int crb = i + LPS[i];
//看是否能继续扩展 中心扩展算法
while(clb&&crb <2*len && exs[clb - 1] == exs[crb + 1])
{
clb--;crb++;LPS[i]++;
}
if(crb > rb)
{
//更新中心位置 左右边界
center = i;rb = crb;lb = clb;
}
ret = max(LPS[i],ret);
}
return ret;
}
int main()
{
int cnt = 0;
while(scanf("%s",&s[0]) && strcmp(s,"END"))
{
int ans = manacher(s);
printf("Case %d: %d\n",++cnt,ans);
}
return 0;
}
主要参考了geekforgeeks上的教程
英文基础好的可以看:geekforgeeks MANACHER
网上模板
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char s[11000002];
char s_new[21000002];//存添加字符后的字符串
int p[21000002];
int Init() {//形成新的字符串
int len=strlen(s);//len是输入字符串的长度
s_new[0]='$';//处理边界,防止越界
s_new[1]='#';
int j=2;
for(int i=0;i<len;i++) {
s_new[j++]=s[i];
s_new[j++]='#';
}
s_new[j]='\0';//处理边界,防止越界(容易忘记)
return j;// 返回s_new的长度
}
int Manacher() {//返回最长回文串
int len=Init();//取得新字符串的长度, 完成向s_new的转换
int max_len=-1;//最长回文长度
int id;
int mx=0;
for(int i=1;i<=len;i++) {
if(i<mx)
p[i]=min(p[2*id-i],mx-i);//上面图片就是这里的讲解
else p[i]=1;
while(s_new[i-p[i]]==s_new[i+p[i]])//不需边界判断,因为左有'$',右有'\0'标记;
p[i]++;//mx对此回文中点的贡献已经结束,现在是正常寻找扩大半径
if(mx<i+p[i]) {//每走移动一个回文中点,都要和mx比较,使mx是最大,提高p[i]=min(p[2*id-i],mx-i)效率
id=i;//更新id
mx=i+p[i];//更新mx
}
max_len=max(max_len,p[i]-1);
}
return max_len;
}
int main()
{
scanf("%s",&s);
printf("%d",Manacher());
return 0;
}
4. 构造后数组与原数组关系
- 回文串长度
len = lps[i]-1
- 数组下标关系
old_arr_idx = idx/2