参考自:https://leetcode-cn.com/problems/shortest-palindrome/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by–44/
214.最短回文串
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。
来源:LeetCode
网址:https://leetcode-cn.com/problems/shortest-palindrome/
算法一:马拉车算法
模板:
/**
* 预处理
* @param s:原字符串
* @return:返回预处理结果
*/
private String preProcess(String s){
int n = s.length();
if (n == 0)
return "^$";
String ans = "^";
for (int i = 0; i < n; i++) {
ans += "#" + s.charAt(i);
}
ans += "#$";
return ans;
}
/**O(n)
* Manacher
*/
private String Manacher(String s){
String str = preProcess(s);//预处理
int n = str.length();
int[] p = new int[n];
int center = 0,L = 0,R = 0;
int mirror;
for (int i = 1; i < n-1; i++) {
mirror = 2*center- i;//i的镜像位置
if (i < R){//当i小于R时
p[i] = Math.min(R-i,p[mirror]);//如果p[mirror]没有越界的,则p[i]=p[mirror],否则还需要继续中心扩散
}else p[i] = 0;
while (str.charAt(i+p[i]+1) == str.charAt(i-p[i]-1)){//中心扩散
p[i]++;
}
if (i+p[i] > R){//如果超出右边界,更新center和R
center = i;
R = i + p[i];
L = i - p[i];
}
}
//找出P的最大值(最长回文子串的半径)
int maxLen = 0;
int start = 0;
for (int i = 1; i < n-1; i++) {
if (p[i] > maxLen){
maxLen = p[i];
start = (i-p[i])/2;
}
}
return s.substring(start,start+maxLen);
}
本题的应用:
思想:
- 设原字符串为:
s = "abbacd"
- 通过适当修改Manacher算法,获得包含第一个字符的最长回文子串
pal = "abba"
,s
比pal
多了剩下的s2 = "cd"
- 也就是说正是
s2
造成了s
不是回文串,那么接下来就很清楚了,将s2
取逆得到s3 = "dc"
,将s3
接到s
的前面得到ans = s3 + s = "dcabbacd"
public String shortestPalindrome(String s){
String pal = Manacher(s);
return new StringBuilder(s.substring(pal.length())).reverse()+s;
}
private String preProcess(String s){
int n = s.length();
if (n == 0)
return "^$";
String ans = "^";
for (int i = 0; i < n; i++) {
ans += "#" + s.charAt(i);
}
ans += "#$";
return ans;
}
private String Manacher(String s){
String str = preProcess(s);//预处理
int n = str.length();
int[] p = new int[n];
int center = 0,L = 0,R = 0;
int mirror;
for (int i = 1; i < n-1; i++) {
mirror = 2*center- i;//i的镜像位置
if (i < R){//当i小于R时
p[i] = Math.min(R-i,p[mirror]);//如果p[mirror]没有越界的,则p[i]=p[mirror],否则还需要继续中心扩散
}else p[i] = 0;
while (str.charAt(i+p[i]+1) == str.charAt(i-p[i]-1)){//中心扩散
p[i]++;
}
if (i+p[i] > R){//如果超出右边界,更新center和R
center = i;
R = i + p[i];
L = i - p[i];
}
}
//找出P的最大值(最长回文子串的半径)
//针对本题,这里需要修改一下,该回文串必须是从头开始的
int maxLen = 0;
int start = 0;
int tmp = 0;
for (int i = 1; i < n-1; i++) {
tmp = (i-p[i])/2;
if (p[i] > maxLen && tmp == 0){//从头开始的
maxLen = p[i];
start = tmp;
}
}
return s.substring(start,start+maxLen);
}
算法二:KMP
模板:
private static int KMP(String text,String pattern){
int[] next = getNext(pattern);
for (int i = 0,j = 0; i < text.length(); i++) {
while (j > 0 && text.charAt(i) != pattern.charAt(j))
j = next[j-1];
if (text.charAt(i) == pattern.charAt(j))
j++;
if (j == pattern.length())
return i - j + 1;
}
return -1;
}
private static int[] getNext(String pat){
int[] next = new int[pat.length()];
next[0] = 0;
for (int i = 1,j = 0; i < pat.length(); i++) {
while (j > 0 && pat.charAt(i) != pat.charAt(j))
j = next[j-1];
if (pat.charAt(i) == pat.charAt(j))
j++;
next[i] = j;
}
return next;
}
本题的应用:
要想了解KMP最重要的就是了解next数组干了些什么?
这里呢,我也不讲什么前缀和后缀了,想了解更多点击这里
.
现在你只需要知道,若 next[j] = k,则 s[0 ~ k-1] = s[j-k ~ j-1]
举个栗子,s = "abcecaba"
那么next={0,0, 0, 0, 0, 1, 2, 1}
;
next[7] = 2
, 根据定义得s[0:1]=s[5:6]
,即 “abcecaba”,
所以,你可以把next[]看成是字符串s的部分匹配数组,而next[j]中存的数值就是以下标j为终止的最长部分匹配长度。
·
所以,现在再来看看这题,假设s = "aacecaaa"
,
我们定睛一看,发现s1 = s[0:6] = "aacecaa"
是回文子串,还剩下了s2 = s[7] = "a"
,如果我们在s
前面再补上s[7]
,就可以得到我们要找的最短回文串ans = s2 + s = "aaacecaaa"
;
·
此时,如果我们对ss = s + "#" +s' = "aacecaaa#aaacecaa"
(其中s’为s的逆串)求它的部分匹配数组next={0, 1, 0, 0, 0, 1, 2, 2, 0, 1, 2, 2, 3, 4, 5, 6, 7}
,分析一下,就可以发现next[16]=7,即"aacecaaa#aaacecaa"
.
即s1 = ss[0:next[16]-1] = s[0:end-1] = "aacecaa",end=next[16]
,那么剩下的s2 = s[end:] = "a"
,所以ans = s2 + s =s.substring(next[ss.length()-1]) + s = aaacecaaa
public String shortestPalindrome(String s){
String ss = s + "#" + new StringBuilder(s).reverse();
int add = getLast(ss);
return new StringBuilder(s.substring(add)).reverse()+s;
}
private int getLast(String s){//KMP的next数组,求最长前后缀
//若 next[j] = k,则 w[0 ~ k-1] = w[j-k ~ j-1]
int n = s.length();
int[] next = new int[n];
for(int i = 1,j = 0;i < n;i++){
while (j > 0 && s.charAt(i) != s.charAt(j))
j = next[j-1];
if (s.charAt(i) == s.charAt(j))
j++;
next[i] = j;
//System.out.print(next[i]+" ");
}
return next[n-1];
}