最长回文串

              (1)思路

                        1. 因为字符串长度为奇数和偶数时处理方式不同,所以这里可以使用插入特殊字符的方法,使得每个字符串在处理之后长度都为奇数。处理方法为:在任意两个字符之间和字符串的开始和结尾插入'#'。(因为设字符为n个,那么共有n-1个间隙,n+(n-1)必然为奇数,再加上首尾'#'还是奇数)。因为要防止越界,所以在修改后的字符串首尾再加上边界,开始为'$',结尾为'@'

	private String chuli(String str) 
	{
		StringBuffer sb = new StringBuffer();
		
		sb.append('$');                   //加入开始标识
		for(int i=0; i<str.length(); i++)
		{
			sb.append('#');           //每个字符之前加入#
			sb.append(str.charAt(i));
		}
		sb.append('#');                  //末尾加入#
		sb.append('@');                  //加入结束符
		
		return sb.toString();
	}
                        测试:

		String str = "abba";
		str = chuli(str);
		for(int i=0; i<str.length(); i++)
			System.out.print(str.charAt(i)+" ");

                       结果为:

$ # a # b # b # a # @ 
                        2. 核心算法

                           1>定义数组p[i]:表示以str[i]为中心的最大回文串的长度(包含字符str[i]),pi表示已经求得的拥有最长回文串的字符的下标,mx表示最长回文串所能达到的最右边的边界
                           2>

                                

                             pi是最长回文串的中心(淡蓝色),如果以j为中心的最大回文串如上图所示,那么处于i处的情况和j处相同(因为pi的两侧是对称的)。这样求i的时候就可以利用j的结果了,此处显然j = 2*pi-i

                             mx是最长回文串的右边界,因为i<mx所以在求p[i],即以i为中心的回文串时,就可以利用关于pi的对称点j的信息。因为图中蓝色标志长度相同,均为mx-i,所以当mx-i>p[j],即图中所示情况时,p[i] = p[j],其余能不能再延长,就只能一个个字符判断了。

                           3>

                            

                            这种情况虽然mx>i,但是mx-i<p[j],也就是说最长回文串并没有全部包含以j为中心的回文串,所以此处最长只能选择mx-i为半径,即p[i] = mx-i

                           4>最后一种情况:mx-i<0,即i在最长回文串的外面,所以此时就只能从头开始算了,即p[i] = 1


              (2)代码

	@Test
	public void huiwen()
	{
		String str = "abba";
		str = chuli(str);
		
		int mx = 0;
		int pi = 1;
		int[] p = new int[str.length()];
		
		for(int i=1; i<str.length(); i++)  //因为已经将字符串第0个字符设置为$,所以直接从第1个开始
		{
			if(mx>i)                  //如果i在mx内,即可以利用以前求得的结果
			{
				if(mx-i>p[pi*2-i])             //整个i的对称点j = pi*2-i,都在最长回文串中
					p[i] = p[pi*2-i];
				else                           //一部分在最长回文串外
					p[i] = mx-i;
			}
			else                     //因为i在mx外,所以不能利用之前求得的结果,那么就直接设为1
				p[i] = 1;
			
			while(i-p[i]>0 && i+p[i]<str.length() && str.charAt(i+p[i])==str.charAt(i-p[i]))  //设置完p[i]的初始值之后,再一次扩展两边,看看是                                                                                                          //否能够将回文串扩大
				p[i]++;
			
			if(mx<i+p[i])              //如果当前求得的回文串长度大于之前最大的,那么更新最大值
			{
				mx = i+p[i]-1;    //i+p[i]-1为右边界
				pi = i;
			}
		}
		
		int maxLen = p[0];
		for(int i=1; i<p.length; i++)              //选出其中最长的半径
			maxLen = Math.max(maxLen, p[i]);
		
		System.out.println(maxLen-1);              //maxLen-1就是原字符串的最大回文串长度,下面证明
	}

              (3)注意

                        1.因为p[i]记录的是插入分隔符之后的回文串的半径,所以以i为中心的回文串的长度为2*p[i]-1.如bb->$#b#b#@,中间#的半径为3,所以回文串长度为2*3-1 = 5

                        2.因为#b#b#长度-1后,就正好是原来字符串长度的2倍,即((2*p[i]-1)-1)/2 = p[i]-1,得证。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值