问题:给定一个字符串str,只允许在首部添加字符,求构造的最短回文串的长度。
思路:找到以str[n-1]为结尾的最长回文串,设为str[i,n-1];再用前半部分的翻转链接到该串即可:reserve(str[0,i-1])+str
实现:若str[i,n-1]是以str[n-1]为结尾的最长回文串,则反转str后,有reserve(str)[0,n-i-1] equals str[i,n-1],于是转化为reserve(str)+str的最长公共前后缀问题。为避免前后缀越界,用两个不同的特殊字符标记。
class Solution {
public String shortestPalindrome(String s) {
if(s.length()==0){
return s;
}
StringBuilder sb=new StringBuilder(s);
sb.append('#');//加一个标识防止s+reverse(s)得到的最长公共前后缀长度超过原串
sb.append(new StringBuilder(s).reverse().toString());
sb.append('!');//在最后任意加一个字符(遵循kmp的next数组的语义,求目标最长公共前后缀),不与标志字符‘#’相同即可。
return new StringBuilder(s.substring(getNext(sb.toString())[sb.length()-1])).reverse().append(s).toString();
}
/**
* kmp算法.通过模式串得失配next数组--‘next[i],在i位置发生失配’,pattern[0,i-1]的最长公共前后缀'长度',又下一个字符的索引值==长度
* next[i],pattern[0,i-1]的最长公共前后缀的长度。notice:length((前缀|后缀))<=length(str)-1,即前缀后缀的概念不含字符串本身
*/
private int[] getNext(String pattern){
int[] next=new int[pattern.length()];
next[0]=-1;
int pre;//待验证字符索引,关键变量
for(int i=1;i<pattern.length();i++){
pre=next[i-1];
while(pre!=-1 && pattern.charAt(i-1)!=pattern.charAt(pre)){//比较pattern[i-1]与pattern[pre]
pre=next[pre];//kmp核心
}
if(pre==-1){
next[i]=0;
}else{
next[i]=pre+1;
}
}
return next;
}
}
变种:若只能从尾部加字符,则转化为求str+reserve(str)的最长公共前后缀