Longest Palindromic Substring (M)
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example 1:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example 2:
Input: "cbbd"
Output: "bb"
题意
给定一个字符串s,要求输出该字符串中的最大回文子串。
思路
- 暴力法。复杂度 O ( N 3 ) O(N^3) O(N3)
- 动态规划法。设若P[i][j]==true,则字符串s中从下标i到下标j构成的子串为回文子串,则很容易得到状态转移方程及边界条件如下,复杂度为
O
(
N
2
)
O(N^2)
O(N2):
P [ i ] [ j ] = ( P [ i + 1 ] [ j − 1 ] & & s [ i ] = = s [ j ] ) P[i][j] = (P[i+1][j-1] \ \&\&\ s[i] == s[j]) P[i][j]=(P[i+1][j−1] && s[i]==s[j])
P [ i ] [ j ] = { t r u e , i = = j t r u e , j = = i + 1 & & s [ i ] = = s [ j ] f a l s e , j = = i + 1 & & s [ i ] ! = s [ j ] P[i][j] = \begin{cases} true, &i==j\\ true, &j==i+1 \ \&\&\ s[i]==s[j]\\ false, &j==i+1 \ \&\&\ s[i]!=s[j] \end{cases} P[i][j]=⎩⎪⎨⎪⎧true,true,false,i==jj==i+1 && s[i]==s[j]j==i+1 && s[i]!=s[j] - 中间扩展法。遍历字符串的每一个字符,奇数子串以该字符为中心向两边扩展搜索,直到两端字符不相同;偶数子串以该字符与下一个字符为中心向两遍扩展搜索,直到两端字符不相同。复杂度为 O ( N 2 ) O(N^2) O(N2)。
- 马拉车算法 - Manacher’s Algorithm,复杂度为 O ( N ) O(N) O(N)。
代码实现 - 动态规划
class Solution {
public String longestPalindrome(String s) {
// 特殊情况排除
if (s == null || s.isEmpty()) {
return "";
}
int maxLen = 0;
int left = 0;
int right = 0;
boolean[][] P = new boolean[s.length()][s.length()];
// 以子串长度及开始位置的变化进行循环
for (int len = 1; len <= s.length(); len++) {
for (int i = 0; i + len - 1 < s.length(); i++) {
int j = i + len - 1;
// 根据状态转移方程及边界条件生成数组P的值
if (len == 1) {
P[i][j] = true;
} else if (len == 2) {
P[i][j] = (s.charAt(i) == s.charAt(j));
} else {
P[i][j] = (P[i + 1][j - 1] && s.charAt(i) == s.charAt(j));
}
if (P[i][j] && len > maxLen) {
maxLen = len;
left = i;
right = j;
}
}
}
return s.substring(left, right + 1);
}
}
代码实现 - 中间扩展
class Solution {
public String longestPalindrome(String s) {
// 特殊情况处理
if (s == null || s.isEmpty()) {
return "";
}
int maxLen = 0;
int left = 0;
int right = 0;
for (int i = 0; i < s.length(); i++) {
// 奇数子串情况
int len1 = fromCenter(i, i, s);
// 偶数子串情况
int len2 = fromCenter(i, i + 1, s);
int len = Math.max(len1, len2);
if (len > maxLen) {
maxLen = len;
left = i - (len - 1) / 2;
right = i + len / 2;
}
}
return s.substring(left, right + 1);
}
// 从目标字符向两边扩展,返回长度
public int fromCenter(int i, int j, String s) {
while (i >= 0 && j < s.length() && s.charAt(i) == s.charAt(j)){
i--;
j++;
}
return j - i - 1;
}
}
代码实现 - Manacher
class Solution {
public String longestPalindrome(String s) {
// 特殊情况排除
if (s == null || s.isEmpty()) {
return "";
}
String t = transform(s);
int[] p = new int[t.length()];
// 中心位置
int C = 0;
// 右边界
int R = 0;
// 回文串最大半径
int maxLen = 0;
// 最长回文串对应中心
int pos = 0;
for (int i = 0; i < t.length(); i++) {
// i关于C的对称点
int j = 2 * C - i;
// 分三种情况进行赋值
p[i] = R >= i ? Math.min(p[j], R - i) : 0;
// 回文半径可增加的情况
while (i + p[i] + 1 < t.length() && i - p[i] - 1 >= 0
&& t.charAt(i + p[i] + 1) == t.charAt(i - p[i] - 1)) {
p[i]++;
}
if (p[i] > maxLen) {
maxLen = p[i];
pos = i;
}
// 更新中心点和右边界
if (i + p[i] > R) {
C = i;
R = i + p[i];
}
}
int left = (pos - maxLen) / 2;
int right = (pos + maxLen - 1) / 2;
return s.substring(left, right + 1);
}
// 字符串转换
public String transform(String s) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
builder.append('#').append(s.charAt(i));
}
builder.append('#');
return builder.toString();
}
}
参考 - Manacher