最长回文子串问题(动态规划 和machacher匹配)

本文深入讲解Manacher算法,一种高效查找最长回文子串的方法。文章解析算法原理,包括特殊字符插入、辅助数组运用及递推过程,并通过实例阐述边界处理技巧。

1.动态匹配

  最长回文子串 在一个字符串中 找到回文字符串 ABCBA 就是一个回文子串的形式;在最长回文匹配问题上使用时间复杂度最短算法 o(N),相比于中心扩展法,不管长度是奇数还是偶数 使用manacher算法将其 全部情况转换成 奇数进行处理。
      1.在每个字符的两边插入一个特殊符号 ABC----> #A#B#C#(奇数或者偶数字符串转换成奇数的字符串)。
      2.借助辅助数组P[i] 记录以字符S[i] 为中心的最长回文的长度 ;id 表示p[1...j] 最大回文长度的位置,mx =id+p[id] 最大回文长度的边界
根据macnacher算法--  p[i]>=min(p[2*id-i],mx-i)

3.如何进行递推p[i] 矩阵:

    假设现在求出P[1,...i],现在需要求 后面的P[i+k] 的值,再假设现在有个指针k,从1循环到P[i] 
  出现之下的三种情况:
   如图1所示:黑色部分是回文子串,两段红色区间是对等相等的

   ①rad[i]-k<rad[i-k]: 
         rad[i-k] 的范围是青色的部分,黑色的部分是回文,且青色的部分超过了黑色的部分 radi+K]至少为,即橙色的部分。那橙色以外的部分就不是了吗?这是肯定的,因为如果橙色以外的部分也是回文的,那么根据青色和红色部分的关系,可以证明黑色部分再往外延伸一点也是一个回文子串,这肯定是不可能的,所以 rad[i+k]=rad[i]-k;

②  rad[i] rad[i k]: rad[i+k]=rad[i-k];

如图2,rad[i-k]的范围为青色,因为黑色的部分是回文的,且青色的部分在黑色的部分里面,根据定义,很容易得出:rad[i k] rad[i k]。根据上面两种情况,可以得出结论:当rad[i] != rad[i k]的时候,rad[i k] min(rad[i] k, rad[i k])。



③  rad[i] rad[i k]: 有可能比原来的长度要长 rad[i+k]>=rad[i-k];

如图,通过和第一种情况对比之后会发现,因为青色的部分没有超出黑色的部分,所以即使橙色的部分全等,也无法像第一种情况一样引出矛盾,因此橙色的部分是有可能全等的。但是,根据已知的信息,我们不知道橙色的部分是多长,因此就需要再去尝试和判断了。


 之上理解 告诉macnacher算法的 回文最大长度的边界 与 新增入的p[i] 的位置关系 ,根据July的编程之法的理解中

     id 表示最大回文长度的位置 ,求p[i]位置的值 假设已经知道p[0,1...i-1]的值 

     mx 表示id+p[id] :最大回文长度的边界


    ① 根据对称性 求出 i关于id 位置的 对称点 j=id-(i-id)=2id-i; 总共分为 mx>i 和mx<=i


    ② 当mx>i:  同时候 mx-i>p[i]  说明以s[j]为中心回文子串的包含在S[id]为中心的回文子串中 ,i与j对称

    以s[i]为中心回文子串的包含在S[id]为中心的回文子串中对应上述过程中2 过程 S[i] 在最大回文长度之内。


 ③mx>i 但是P[i]>mx-i 如果s[i] 为中心回文子串超出边界 (上述过程中1) 当p[i]>=mx-i, S[j]回文子串不一定会完全被包含在 S[id]之内,
但是根据对称性知道 P[i]>=mx-i 至于超出部分 必须要 i为中心 在一一的进行匹配了。

④ mx<= i ;表示无法对P[i]做成很大的假设,即事实不在这个边界 无法找到关于id位置对称的点 上字符S[j] 与S[i]相等 只能让p[i]=1;
// 动态规划
		for(int len=2;len<=length;len++)// len 表示回文字符串长度,i表示回文子串初始位置
			for(int i=0;i<length-len+1;i++)
			{
				int j=i+len-1;//子串的结束位置
				if(p[i+1][j-1] && str.charAt(i)==str.charAt(j))
				{
					p[i][j]=true;
					maxlength=len;
					start=i;
				}
			}
		 if(maxlength>=2)  
		        return str.substring(start,maxlength);
		return null;
	}
	
	// 中心扩展法,回文的话,那么 其中心位置 前缀和后缀一定是相等,枚举 所有的中心位置
	public static String findLongestOalindrome1(String str)
	{// 字符串是从 0 位置开始进行索引
		if(str==null) return null;
		
		int start=0;int max=0;
		
		int i,j,c = 0;int n=str.length();
		for( i=0;i<str.length();++i)
		{
			// 处理奇数 以 i为中心,向两边扩展
			for(j=0;(i-j)>=0 &&(i+j)<n;++j)
			{
				if(str.charAt(i-j)!=str.charAt(i+j))
					{  break;}
				c=2*j+1;
				
			}
			if(c>max)
			{	
				max=c;
				start=i-j+1;
			}
			
			// 处理偶数abba 明显i=b,j=0 判断 i-j(本身) 与 i+j+1(i+1)
			for(j=0;(i-j)>=0 &&(i+j+1)<n;++j)
			{	if(str.charAt(i-j)!=str.charAt(i+j+1))
					{break;}
			   c=2*j+2;
			   
			}
			if(c>max)
				{ 
				   max=c;
				   start=i-j+1;
				}
		}
		System.out.println(start);
		return str.substring(start, max);
	}
	public static String findLongestOalindrome2(String str)
	{
		if(str == null||str.length() == 0)
        return str;
		StringBuilder sb=new StringBuilder();
       sb.append('#');
     for(int i=0;i<str.length();i++)
        {
        sb.append(str.charAt(i));
        sb.append('#');
     }
		str=sb.toString();
		
		int n=str.length();
		
		// Manancher id 表示P[i] 最大回文子串位置 mx =max(p[i])+id 最大回文子串的边界
		int id=0,mx=0,i;
		int p[]=new int[str.length()];
		
		// 从左往右遍历 str ,对于 i位置现已经知晓 p[0,1...i-1]中的值
		for( i=1;i<str.length();i++)
		{
				if(mx>i)
				{
					p[i]=Math.min(p[2*id-i],mx-i);
				}
				else
				{
					p[i]=1;
				}
				// p[i]>=mx-i
				while( i+p[i]<n &&i-p[i]>=0&&str.charAt(i+p[i])==str.charAt(i-p[i]))
					p[i]++;
				if(p[i]+i>mx)
				{
					mx=p[i]+i;
					id=i;
				}
		}
		
		/*****进行输出 找到P[i]最大值所对应的位置就是 最大回文子串位置**/
		int max=0;
        for( i=1;i<p.length;i++)
            {
            if(p[i]>max)
                {
                max=p[i];
                id=i;
            }
        }
	int start=id-max+1;
        int end=id+max-1;
        StringBuilder sb2=new StringBuilder();
        for( i=start;i<=end;i++)
            {
            if(str.charAt(i)!='#')
                sb2.append(str.charAt(i));
        }
        return sb2.toString();
	}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值