当前位置:网站首页>笔试面试算法经典–最长回文子串
笔试面试算法经典–最长回文子串
2022-06-28 14:49:00 【全栈程序员站长】
大家好,又见面了,我是你们的朋友全栈君。
回文的定义
正读和反读都相同的字符序列为“回文”,如“abba”、“abccba”是“回文”,“abcde”和“ababab”则不是“回文”。字符串的最长回文子串,是指一个字符串中包含的最长的回文子串。例如“1212134”的最长回文子串是“12121”。下面给出了三种求最长子串的方法。
解法1(中心扩展法)
时间复杂度O(n^2),空间复杂度为O(1)。中心扩展法的思路是,遍历到数组的某一个元素时,以这个元素为中心,向两边进行扩展,如果两边的元素相同则继续扩展,否则停止扩展。如下图:当遍历到3时
但是中心扩展法有一个问题,如下图:
1,2,2,1是一个回文串,然而找不到对称中心,这样以一个元素为中心向两边扩展就不好用了,这里有一种比较方便的处理方式,就是对1,2,2,1进行填充,比如说用#进行填充如下:
如下图这样填充之后就又成了一个中心对称的回文串,因此对于一个串进行处理时,先进行扩充,这样可以使中心扩展时比较方便,不用对非中心对称的子串进行特殊处理。假设填充前的子串长度为 len 那么扩充或的长度为:2 * len+1,由这个关系也可以很方便的得到扩充前回文串的长度。 代码
public void palindrome(String str)
{
if(str==null||str.length()==0)
return ;
//如果str为null 或长度为0直接返回。
StringBuffer sb=new StringBuffer(str);
for(int i=0;i<str.length();i++)
{
sb.append("#");
sb.append(str.charAt(i));
}
//对给出的串进行填充。
sb.append("#");
char chs[]=sb.toString().toCharArray();
int max_len=0;
for(int i=0;i<chs.length;i++)
{
//遍历到位置i时,对i进行中心扩展
max_len=Math.max(subpalidromelen(chs,i), max_len);
//subpalidromelen(chs,i),以i为中心进行中心扩展,max_len 保存最长回文子串的长度。
}
System.out.println(max_len);
}
//中心扩展函数
public int subpalidromelen(char []chs,int i)
{
int len=0;
for(int k=0;k<=i&&k<(chs.length-i);k++)
{
if(chs[i-k]==chs[i+k])
{
len++;
}
else {
break;
}
}
return len-1;
//因为是填充之后的串,填充之前的回文子串值为len-1。
}
上面代码做两点说明: ①palindrome()对给出的字符串填充后进行遍历,对遍历的每一个元素调用中心扩展函数获得回文子串的值,并保存最长回文子串的值。
②subpalidromelen()中心扩展函数,返回回文子串的长度。
解法2(动态规划)
时间复杂度O(n^2),空间复杂度O(n^2),如果用f[i][j] 保存子串从i 到j是否是回文子串,那么在求f[i][j] 的时候如果j-i>=2时,如果 f[i][j] 为回文,那么f[i+1][j-1],也一定为回文。否则f[i][j]不为回文。如下图:
因此可得到动态规划的递归方程:
代码:
public void palindrome1(String str)
{
if(str==null||str.length()==0)
return ;
char chs[]=str.toCharArray();
int max_len=0;
boolean f[i][j]=new boolean[chs.length][chs.length];
for(int j=0;j<str.length();j++)
{
int i=0;
f[j][j]=true;
//一个元素肯定是回文串。
for(;i<j;i++)
{
f[i][j]=(chs[j]==chs[i]&&(j-i<2||j>0&&f[i+1][j-1]));
//如果chs[j]==chs[i]当串的长度小于等于2时,肯定是回文子串,如 1,1,就是回文串。
//如果长度大于2时,则需要判断f[i+1][j-1]是不是回文。
if(f[i][j])
{
max_len=Math.max(max_len, j-i+1);
}
//max_len保存最大回文子串的长度。
}
}
System.out.println(max_len);
}
两点说明:
①当子串的长度为1时肯定为回文子串,对应上面的 f[j][j] = true 。当子串的长度为 2 且 里面的两个元素相同时,则也是回文子串。对应上面的 f[i][j]= chs[i]&&(j-i<2).
②当串的长度大于2时,如串为121时,要判断chs[j]==chs[i]&&f[i+1][j-1]),依赖子串。
Manacher
时间复杂度O(n),空间复杂度O(n)观察上面的中心扩展法,发现遍历到每一个元素的时候都要进行一次中心扩展,完全没有利用前面回文子串的信息,Manacher算法的核心思想,就是利用前面遍历的时候产生的回文子串,如下图:
上图中已经求出了红色部分3的回文子串,现在如何求3右边的1,2,1的回文子串呢,利用回文子串的对称性,如下图:
①2*id-i,id , i表示的是数组的下标,2*id-i 与 i 关于 id对称,如果用p[i],表示i位置的最长回文子串,则在上图的这种情况下p[i]=p[2*id-i],由于 p[2*id-i] 在3的左边已经求出来了,因此p[i]很快就可以得到,然而当要求右边2的最长回文的时候如下图:
②发现右边2的回文子串并不等于左边2的回文子串,而且比左边的要长,还有下面的这种情况:
③左边2的回文怎么比右边2的回文长。
综合上面三种情况:其实可以的到下面的公式
由于manacher算法与上面中心扩展法有一样的问题,所以先向字符串中插入#,如下图:
在上面的基础上: 引入一个辅助变量MaxRight,表示当前访问到的所有回文子串,所能触及的最右一个字符的位置。另外还要记录下MaxRight对应的回文串的对称轴所在的位置,记为pos,它们的位置关系如下。
我们从左往右地访问字符串来求RL,假设当前访问到的位置为i,即要求RL[i],在对应上图,i必然是在po右边的(obviously)。但我们更关注的是,i是在MaxRight的左边还是右边。我们分情况来讨论。
1)当i在MaxRight的左边
情况1)可以用下图来刻画:
我们知道,图中两个红色块之间(包括红色块)的串是回文的;并且以i为对称轴的回文串,是与红色块间的回文串有所重叠的。我们找到i关于pos的对称位置j,这个j对应的RL[j]我们是已经算过的。根据回文串的对称性,以i为对称轴的回文串和以j为对称轴的回文串,有一部分是相同的。这里又有两种细分的情况。
以j为对称轴的回文串比较短,短到像下图这样。
这时我们知道RL[i]至少不会小于RL[j],并且已经知道了部分的以i为中心的回文串,于是可以令RL[i]=RL[j]。但是以i为对称轴的回文串可能实际上更长,因此我们试着以i为对称轴,继续往左右两边扩展,直到左右两边字符不同,或者到达边界。
以j为对称轴的回文串很长,这么长:
遇到这种情况,说明以i为对称轴的回文串还没有任何一个部分被访问过,于是只能从i的左右两边开始尝试扩展了,当左右两边字符不同,或者到达字符串边界时停止。然后更新MaxRight和pos。
代码:
public void manacher(String str)
{
if(str==null||str.length()==0)
{
return;
}
int len=str.length();
StringBuffer sb=new StringBuffer(str);
for(int i=0;i<len;i++)
{
sb.append("#");
sb.append(str.charAt(i));
}
sb.append("#");
//先往串中插入#后不用再区分两种情况了。
int id=0,i=0,mx=0;
int n=sb.length();
int p[]=new int[n];
int max_len=0;
char chs[]=sb.toString().toCharArray();
for(i=1;i<n;i++)
{
if(mx>i)
p[i]=Math.min(p[2*id-i], mx-i);
//如果i在最大回文子串的中间,就可以利用上面的分析p[i]表示i的最长回文子串的长度
else {
p[i]=1;
//否则p[i]=1,
}
for(;(i-p[i]>=0&&i+p[i]<n)&&(chs[i-p[i]]==chs[i+p[i]]);p[i]++)
;
//在上面的基础上进行扩展。
if(i+p[i]>mx)
{
mx=p[i]+i;
id=i;
}
//如果i+p[i]>mx,就更新mx。
max_len=Math.max(max_len, p[i]-1);
}
System.out.println(max_len);
}
参考文献:https://segmentfault.com/a/1190000003914228
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/132955.html原文链接:https://javaforall.cn
边栏推荐
- 新零售线下店逆势起飞,通膨乌云下的消费热情
- Technical trendsetter
- 美因基因港交所上市:市值43亿港元 IPO被市场忽略
- js 判断字符串为空或者不为空
- [MySQL learning notes 24] index design principles
- 解决Unable to create process using ‘D:\Program File
- Seata数据库中出现以下问题要怎么解决啊?
- [C language] nextday problem
- @Controlleradvice + @exceptionhandler handles controller layer exceptions globally
- 机器人的运动范围(DFS)
猜你喜欢
Performance comparison of deep learning models on cat and dog image data sets
老板囑咐了三遍:低調、低調、低調
Mingchuangyou products passed the listing hearing: seeking dual main listing with an annual revenue of 9.1 billion
[C language] nextday problem
成龙和快品牌,谁才是快手的救星?
Q-Tester 3.2:适用于开发、生产和售后的诊断测试软件
openGauss内核:SQL解析过程分析
动力电池,是这样被“瓜分”的
Ding! Techo day Tencent technology open day arrived as scheduled!
币圈大地震:去年赚100万,今年亏500万
随机推荐
字节跳动埋点数据流建设与治理实践
抽奖动画 - 鲤鱼跳龙门
sent2vec教程
哪个证券公司最大最安全 怎么办理开户最安全
Configuration file encryption (simple use of jasypt)
How can I get the stock account opening discount link? Is it safe to open a mobile account?
Q-tester 3.2: applicable to development, production and after-sales diagnostic test software
Mingchuangyou products passed the listing hearing: seeking dual main listing with an annual revenue of 9.1 billion
Which securities company is the largest and safest? How to open an account is the safest
物联网低代码平台常用《组件介绍》
验证回文串
Angers medical sprint scientific innovation board: annual revenue of RMB 300million and proposed fund raising of RMB 770million
PMP认证证书的续证费用是多少?
functools:对callable对象的高位函数和操作(持续更新ing...)
2022 questions d'examen pour les cuisiniers chinois (Senior) et l'examen de simulation en ligne
Quantum frontier hero spectrum - "light quantum Explorer" McMahon: turning any physical system into a neural network
Rails进阶——框架理论认知与构建方案建设(一)
Opening and closing principle
叮!Techo Day 腾讯技术开放日如约而至!
云杉网络DeepFlow帮助5G核心网和电信云构建可观测性