中等-5-最长回文子串
给定一个字符串 s
,找到 s
中最长的回文子串。
一、暴力解法
最暴力的解法是判断s的每个子串是否为回文串,复杂度为立方级,在此不做实现。
这里实现的是中心扩散的思想,即利用回文串中心对称的性质,遍历s的每个字符,其作为回文串的中心,依次向两边扩展更新当前回文串,若超过res则更新res。
复杂度为O(n2)。
以下代码花了几分钟写的,未优化。
class Solution { public String longestPalindrome(String s) { int len = s.length(); String res = ""; if(len==0) return res; int resLen = 0;
// 奇回文子串 for(int i=0; i<len; i++){ int cur = 1; int left = i-1; int right = i+1; while(left>=0&&right<=len-1&&s.charAt(left)==s.charAt(right)){ left--; right++; cur += 2; } if(cur>resLen){ resLen = cur; res = s.substring(++left,right); } }
// 偶回文子串 for(double i=0.5; i<=len-1.5; i++){ int cur = 0; int left = (int) (i-0.5); int right = (int) (i+0.5); while(left>=0&&right<=len-1&&s.charAt(left)==s.charAt(right)){ left--; right++; cur += 2; } if(cur>resLen){ resLen = cur; res = s.substring(++left,right); } } return res; } }
二、动态规划
dp[i][j] 表示 s[i,j](两边闭)是否为回文串。 状态转移方程: dp[i][j] = (s[i] == s[j]) && dp[i + 1][j - 1]。
初始化二维数组对角线为true,并只需维护该对角线以上部分,注意临界范围为 j-i<3 。若该区间为true且长度大于res,则更新目标值。
class Solution { public String longestPalindrome(String s) { int len = s.length(); if(len < 2) return s; int resLen = 1; int begin = 0; boolean[][] dp = new boolean[len][len]; for(int i=0; i<len; i++) dp[i][i] = true; for(int j=1; j<len; j++){ for(int i=0; i<j; i++){ if(s.charAt(i)!=s.charAt(j)){ dp[i][j] = false; } else { if(j - i < 3) dp[i][j] = true; else dp[i][j] = dp[i+1][j-1]; } if(dp[i][j] && j - i + 1 > resLen){ resLen = j - i + 1; begin = i; } } } return s.substring(begin,begin + resLen); } }
三、马拉车算法 Manacher' algorithm
构造T为新字符数组,在每个字符两边加上‘#’,长度n+(n+1)统一为奇回文。 构造P数组对应以T中每个字符为中心的回文串长度。
1、最大半径减1等于最长回文串的长度
2、最长回文字符的起始位置(字符串s中的索引)是中间位置(P或T中的索引)减去半径(P中的值+1)再除以2
这里的P数组是长度,因此无需与半径做转换。
public class Solution { // Transform S into T. // For example, S = "abba", T = "^#a#b#b#a#$". // ^ and $ signs are sentinels appended to each end to avoid bounds checking String preProcess(String s) { int n = s.length(); if (n == 0) return "^$"; String ret = "^"; for (int i = 0; i < n; i++) { ret += "#" + s.substring(i, i + 1); } ret += "#$"; return ret; } public String longestPalindrome(String s) { String T = preProcess(s); int length = T.length(); int[] p = new int[length]; int C = 0, R = 0; for (int i = 1; i < length - 1; i++) { int i_mirror = C - (i - C); int diff = R - i; if (diff >= 0)//当前i在C和R之间,可以利用回文的对称属性 { if (p[i_mirror] < diff)//i的对称点的回文长度在C的大回文范围内部 { p[i] = p[i_mirror]; } else { p[i] = diff; //i处的回文可能超出C的大回文范围了 while (T.charAt(i + p[i] + 1) == T.charAt(i - p[i] - 1)) { p[i]++; } C = i; R = i + p[i]; } } else { p[i] = 0; while (T.charAt(i + p[i] + 1) == T.charAt(i - p[i] - 1)) { p[i]++; } C = i; R = i + p[i]; } } int maxLen = 0; int centerIndex = 0; for (int i = 1; i < length - 1; i++) { if (p[i] > maxLen) { maxLen = p[i]; centerIndex = i; } } return s.substring((centerIndex - 1 - maxLen) / 2, (centerIndex - 1 - maxLen) / 2 + maxLen); } }