最近发生了很多事。博客本来准备用自己搭(想要看的可以访问www.wilverain.com)的,后来发现不管是wordpress还是hexo都不是很喜欢,于是就又回到csdn了。等以后成为全栈再自己写一个吧(划掉
最近开始刷hihocoder里面的题目了,就是跟着hiho一下里面来做,预期是每天做一题。毕竟已经是退役狗了。所以对于每一个hihocoder应该都会写一个博客来记录自己的思路或者学习的过程。
嘛回到正题。
提到最长回文子串,最简单的思路,就是暴力枚举每一个子串,然后判断该子串是否是回文串,但是这样的效率,仅枚举每一个子串就已经达到了O(n^2),在加上判断是否是回文串,其复杂度已经达到O(n^3),这样的效率是非常不可取的。
那么就有了相应的优化,对于每一个回文串,其关于对称中心应该是对称的,那么对于一个字符串,只需要枚举每一个对称中心,即可查找到该对称中心的最常回文子串。时间复杂度为O(n^2)
而Manacher算法更加高效,它能够在O(n)的时间复杂度下找到最长回文字串。
首先介绍下Manacher算法需要用到的数据结构吧。
1.p数组,p[i]表示以i为对称中心所能扩展到的最远的长度,包括str[i]
2.mx,表示在i之前的所有以j(j < i)为对称中心的回文串所能扩展到的最远的距离
3.id,表示当最远距离为mx时,其对称中心为id
数据结构主要就这些,我们用例子来说明Manacher算法吧。如abbacdca
首先,因为最长回文字串可以是奇数长度也可以是偶数长度,而这个过程却经常需要去判断,所以为了避免这个过程,我们将待求的串都变成奇数长度的串。即,在两个字符之间添加'#'字符(也可以是别的字符,只要是原串未出现的字符均可以),那么可以得到新的串:#a#b#b#a#c#d#c#a#。当然也可以再将这个字符串扩展一下,以避免在计算的过程中出现需要判断是否越界的情况。(这里在代码中体现)
得到了这个字符串之后,先去判断mx与当前的i的关系。如果mx <= i,那么我们对于p[i]无能为力,无法直接得到p[i]的准确值,所以将p[i]赋值为1;如果mx > i,那么又有两种情况。
因为mx > i的,那么很明显,以id为对称中心的回文串包含了i,那么其对称点就为j = 2 * id - i;
第一种情况,以j为对称中心的回文子串完全包含于以id为对称中心的回文子串,那么显然我们可以得到p[i] = p[j]
第二种情况,以j为对称中心的回文子串并没有完全包含于以id为对称中心的回文子串,这个时候,p[i] >= mx - i;利用回文串对称的原理很容易得到这个结果。
将这两种情况合并,于是我们可以得到p[i] >= min(mx - i, p[2 * id - i]);
根据这两种情况,我们来计算下刚刚得到的新串的每个元素的p[i]值
# a # b # b # a # c # d # c # a #
1 2 1 2 4 2 1 2 1 2 1 6 1 2 1 2 1
根据我们的观察可以发现,abbaacdca这个字符串的最长回文子串长度显然是5,于是我们可以得到一个结论,max(p[i]) - 1就是最长回文子串的长度。
于是就求出了这个过程。
以下是Manacher的代码:
public static int Manacher( String str ) { int cur = 0, ans = 0, len = str.length(); char[] tmp = new char[2 * len + 1]; tmp[cur++] = '#'; for( int i = 0; i < len; ++i ) { tmp[cur++] = str.charAt(i); tmp[cur++] = '#'; } int mx = 0, id = 0; int[] p = new int[ cur ]; for( int i = 0; i < cur; ++i ) { p[i] = (mx > i ? Math.min( p[2 * id - i] , mx - i) : 1); while( ( i + p[i] < cur && (i - p[i] >= 0)) && (tmp[i + p[i]] == tmp[i - p[i]]) ) ++p[i]; if( i + p[i] > mx ) { mx = i + p[i]; id = i; } ans = Math.max(ans, p[i] - 1); } return ans; }
附上hihocoder-1032-最长回文子串的ac代码:
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; public class Main { public static int Manacher( String str ) { int cur = 0, ans = 0, len = str.length(); char[] tmp = new char[2 * len + 1]; tmp[cur++] = '#'; for( int i = 0; i < len; ++i ) { tmp[cur++] = str.charAt(i); tmp[cur++] = '#'; } int mx = 0, id = 0; int[] p = new int[ cur ]; for( int i = 0; i < cur; ++i ) { p[i] = (mx > i ? Math.min( p[2 * id - i] , mx - i) : 1); while( ( i + p[i] < cur && (i - p[i] >= 0)) && (tmp[i + p[i]] == tmp[i - p[i]]) ) ++p[i]; if( i + p[i] > mx ) { mx = i + p[i]; id = i; } ans = Math.max(ans, p[i] - 1); } return ans; } public static void main(String[] args) throws IOException { // TODO Auto-generated method stub BufferedReader reader = new BufferedReader( new InputStreamReader( System.in ), 1 << 16 ); BufferedWriter writer = new BufferedWriter( new OutputStreamWriter( System.out ), 1 << 16 ); int n = Integer.parseInt(reader.readLine()); for ( int i = 0; i < n; ++i ) { String str = reader.readLine(); int ans = Manacher(str); writer.write(ans + " "); writer.flush(); } } }