最长子回文串

5. Longest Palindromic Substring

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

1
2
3
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

1
2
Input: "cbbd"
Output: "bb"
动态规划

如果一个字符串为回文串,那么除去他的首尾一个字符,得到的新字符依旧是回文串。即对于一个字符串S有

p(i,j) = p(i+1,j-1) and Si = Sj。P(i,j)表示从i到j的字符串。

从表达式可以看出,需要知道p(i,j),必须先知道p(i+1,j-1)。不如将外层循环倒置过来,先求i+1的值,再求j-1的值。

还有一点要注意的是初始值,关于j - i <3。如果长度为1的话,那么这个字符串必然为回文串,如果长度为2的话,又满足Si = Sj 那么他也必然是回文串。即他们不受上述表达式的约束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public String longestPalindrome(String s) {
if(s == null || s.length() < 1){
return "";
}
int len = s.length();
boolean[][] dp = new boolean[len][len];
int subLength = 0;
String res = null;
for(int i = len - 1; i>= 0 ;i--){
for(int j = i; j < len; j++){
dp[i][j] = s.charAt(i) == s.charAt(j) &&(j - i < 3 || dp[i+1][j-1]);

if(dp[i][j] && (res == null || j - i + 1 > res.length())){
res = s.substring(i,j+1);
}
}
}
return res;
}
中心扩展算法

首先先看一下回文串有什么特点,回文串的最大特点就是中心对齐。如“ababa”,他是以a为中心对齐。那么对于“abba”这种偶数的回文串,同样也是中心对齐的。他以两个b之间的字符之间为中心。因此我们可以以数组的每一个元素作为中心,找出最大的回文串。

我们可以设置回文串的头尾指针,start 和 end。如果有比目前的回文串更长的回文串,则修改start和end的的位置。这样子时间复杂度为O(n^2),空间复杂度为O(n)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public String longestPalindrome(String s) {
if(s == null || s.length() < 1){
return "";
}
int sLen = s.length();
int start = 0,end = 0;
for(int i = 0; i < sLen; i++){
int len1 = expandAroundCenter(s,i,i);
int len2 = expandAroundCenter(s,i,i+1);
int len = Math.max(len1,len2);
if(len > end - start){
start = i - (len - 1)/2;
end = i + len/2;
}
}
return s.substring(start,end+1);
}

private int expandAroundCenter(String s,int left,int right){
int L = left,R = right;
while(L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)){
L--;
R++;
}
return R - L -1;
}
马拉车算法(Manacher’s Algorithm)

马拉车算法充分应用了回文串的对称性。和中心扩展法一样,我们以某个节点为中心,分别向两边扩展。为了保持字符的个数为奇数,在字符与字符之间插入特殊符号“#”来保证出力的字符个数是为奇数的。如“abab”变为“#a#b#a#b#”。假设新的字符串为T,我们使用P[i] 表示 T[i]处的回文半径。如以T[i]为中心的最长回文串为T[l,r],则P[i] = r - i + 1。这样求最长回文串的问题就转换为求数组P的问题了。

数组P怎么求,考虑。center 表示之前取得最大回文串的中心位置。right表示最大回文串能到达的最右端的值。

  1. 若i<right,则i之前的值必然计算出来了。我们可以利用回文串的性质。找出点i关于center的对称点j。j = 2* center - i。
    1. 如果P[j] < right -i。即以j为中心的回文串没有超出[left,right]的范围,由回文串的性质知道,两端的对应字符应该是相等的。所以有P[j] = P[i]。
    2. 如果P[j] >= right -i。即表明以j为中心的最大回文串超出了范围。在范围内的部分肯定是复合回文串的性质,但是对于超出的部分,只要从right+1开始一一匹配,从而更新right和对应的center,
  2. 若i > right 无法利用回文串的性质,只能一步一步去匹配。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public String longestPalindrome(String s) {
if(s == null || s.length() < 1){
return s;
}
int sLen = s.length();
StringBuilder t = new StringBuilder();
// t.append("$");
t.append("#");
for(int i = 0; i < sLen; i++){
t.append(s.charAt(i));
t.append("#");
}

int tLen = t.length();
int center = 0;
int right = 0;
int maxCenter =0;
int maxLen = 1;

int[] p = new int[2 *sLen + 1];
for(int i = 0; i < tLen; ++i){

p[i] = i<right?Math.min(p[2*center -i],right -i):1;

while(i - p[i] >= 0 && i + p[i]<tLen && t.charAt(i - p[i]) == t.charAt(i + p[i])){
p[i]++;

}

if(right < i + p[i]){
right = i + p[i];
center = i;
}

if(maxLen < p[i]){
maxLen = p[i];
maxCenter = i;
}

}
return s.substring((maxCenter - maxLen + 1)/2,(maxCenter - maxLen + 1)/2 + maxLen - 1);
}

文章作者: 彭峰
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭峰 !
 上一篇
2021-05-02 彭峰
下一篇 
2021-05-02 彭峰
  目录