0005. Longest Palindromic Substring (M)
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)\)
- 动态规划法。设若P[i][j]==true,则字符串s中从下标i到下标j构成的子串为回文子串,则很容易得到状态转移方程及边界条件如下,复杂度为\(O(N^2)\):
\[P[i][j] = (P[i+1][j-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}
\]
- 中间扩展法。遍历字符串的每一个字符,奇数子串以该字符为中心向两边扩展搜索,直到两端字符不相同;偶数子串以该字符与下一个字符为中心向两遍扩展搜索,直到两端字符不相同。复杂度为\(O(N^2)\)。
- 马拉车算法 - Manacher's Algorithm,复杂度为\(O(N)\)。
代码实现
Java
动态规划
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();
}
}
JavaScript
动态规划
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
if (!s.length) return ''
let max = 0
let ends = [0, 0]
let isPalin = []
for (let i = 0; i < s.length; i++) {
isPalin[i] = []
isPalin[i][i] = true
}
for (let len = 2; len <= s.length; len++) {
for (let i = 0; i + len - 1 < s.length; i++) {
let j = i + len - 1
if (s[i] !== s[j]) {
isPalin[i][j] = false
} else {
isPalin[i][j] = len === 2 ? true : isPalin[i + 1][j - 1]
}
if (isPalin[i][j] && j - i + 1 > max) {
max = j - i + 1
ends = [i, j]
}
}
}
return s.slice(ends[0], ends[1] + 1)
}
中间扩展
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
let max = 0
let ends = []
for (let i = 0; i < s.length; i++) {
let len = Math.max(findLength(s, i, i), findLength(s, i, i + 1))
if (len > max) {
max = len
ends = [i - Math.floor((len - 1) / 2), i + Math.floor(len / 2)]
}
}
return s.slice(ends[0], ends[1] + 1)
}
let findLength = function (s, i, j) {
while (i >= 0 && j < s.length && s[i] === s[j]) {
i--
j++
}
return j - i - 1
}
Manacher
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function (s) {
if (!s.length) return ''
let ends = manacher(transform(s))
return s.slice(ends[0], ends[1] + 1)
}
let manacher = function (s) {
let maxRadius = -1
let center = 0
let right = 0
let radius = new Array(s.length).fill(0)
let ends = []
while (right < s.length) {
let left = 2 * center - right
radius[right] = right > center + radius[center] ? 0 : Math.min(radius[left], center + radius[center] - right)
while (
right + radius[right] + 1 < s.length &&
right - radius[right] - 1 >= 0 &&
s[right + radius[right] + 1] === s[right - radius[right] - 1]
) {
radius[right]++
}
if (radius[right] > maxRadius) {
maxRadius = radius[right]
ends = [right - radius[right], right + radius[right]]
}
if (right + radius[right] > center + radius[center]) {
center = right
}
right++
}
return [Math.floor(ends[0] / 2), Math.floor(ends[1] / 2) - 1]
}
let transform = function (s) {
let t = '#'
for (let c of s) {
t += c + '#'
}
return t
}
参考 - Manacher