NC17 最长回文子串
区别:LC5是返回最大回文子串,NC17是返回最大回文子串长度,难度降低了
分析:
- 暴力破解法
public class Solution1 {
// 暴力破解法:求最长回文子串的长度
public int getLongestPalindrome(String A, int n) {
int len = A.length();
if (len < 2) {
return 1;
}
int maxLen = 1;
char[] cs = A.toCharArray();
for (int i = 0; i < len - 1; i++) {// 最后一个字符没必要枚举了
for (int j = i + 1; j < len; j++) {
// 最长回文串:长度>之前的max,且,是回文串
if (j - i + 1 > maxLen && isPalindrome(cs, i, j)) {
maxLen = j - i + 1;
}
}
}
return maxLen;
}
private boolean isPalindrome(char[] cs, int i, int j) {
while (i < j) {
if (cs[i] != cs[j]) {
return false;
}
i++;
j--;
}
return true;
}
} - 中心扩散法
public class Solution2 {
// 中心扩散法:求最长回文子串的长度
public int getLongestPalindrome(String A, int n) {
if (n < 2) {
return 1;
}
int maxLen = 1;
char[] cs = A.toCharArray();
for (int i = 0; i < n - 1; i++) {
int len1 = getPalindromeCenterLen(cs, n, i, i);// 奇数中心的扩散长度
int len2 = getPalindromeCenterLen(cs, n, i, i + 1);// 偶数中心的扩散长度
len1 = Math.max(len1, len2);
if (len1 > maxLen) {
maxLen = len1;
}
}
return maxLen;
}
private int getPalindromeCenterLen(char[] cs, int len, int left, int right) {
int i = left;
int j = right;
while (i >= 0 && j < len) {
if (cs[i] == cs[j]) {
i--;
j++;
} else {
break;
}
}
// 循环跳出:cs[i]!=cs[j]
// 此时的回文中心长度:j-i+1-2=j-i-1
return j - i - 1;
}
}
- 动态规划法
public class Solution3 {
// 动态规划法:求最长回文子串的长度
public int getLongestPalindrome(String A, int n) {
if (n < 2) {
return 1;
}
int maxLen = 1;
char[] cs = A.toCharArray();
// dp[i][j]:表示s[i][j]是否是回文串
boolean[][] dp = new boolean[n][n];
for (int i = 0; i < n; i++) {
dp[i][i] = true;
}
// 经验:dp区域是正方形的话,通常左下角区域无效不需要再填,因为走过的区域不用再走
for (int j = 1; j <= n - 1; j++) { // 上三角区域,按列从上到下填
for (int i = 0; i < j; i++) {
// 首尾不相等时,必不是回文串
if (cs[i] != cs[j]) {
dp[i][j] = false;
} else {
// 首尾相等时,有2种情况
// 情况1:s[i...j]长度不超过3,不用检查必为回文串
// 情况2:s[i...j]长度大于3,由s[i+1...j-1]来判断
dp[i][j] = j - i + 1 <= 3 || dp[i + 1][j - 1];
}
// 更新max和begin
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
}
}
}
// for (int i = n - 2; i >= 0; i--) {// 从右下往上填也行
// for (int j = n - 1; j > i; j--) {
// }
// }
return maxLen;
}
} - Manacher法
public class Solution4 {
// manachar法:求最长回文子串的长度
public static int getLongestPalindrome(String A, int n) {
if (A == null || n == 0) {
return 0;
}
// 将s转换为加了特殊字符#的字符数组,目的是统一奇偶数的回文中心差异性问题
// 比如:s=”cabac“转化为cs=[#c#a#b#a#c#]
char[] cs = manacherString(A, n);
// pArr[i]是cs[i]每个位置的最大回文半径
// 比如:cs=[#c#a#b#a#c#],pArr=[1,2,1,2,1,6,1,2,1,2,1]
int[] pArr = new int[cs.length];
// pR是cs[i]每个位置的回文右边界的下一个位置
// 比如:cs=[#c#a#b#a#c#],pR=1,3,3,5,5,11(此时pR第一次遍历完cs,之后的pR可以不再更新),11,11,11,11,11
int pR = -1;
// index是最近更新pR时的回文中心位置
// 比如:cs=[#c#a#b#a#c#],index=0,1,1,3,3,5(之后pR不再更新,index也不再更新),5,5,5,5,5
int index = -1;
// max记录pArr[i]中最大值:pArr[i]最大值就能算出最长回文子串长度
int maxLen = Integer.MIN_VALUE;
for (int i = 0; i != cs.length; i++) {
// 第一句代码:每轮循环时,i至少不需要验证的区域,先给到pArr[i],解释如下:
// pR<=i:i超过了pR,无法优化,不用验证的区域就是pArr[i]本事=回文半径为1
// pR>i:i没有超过pR,可以优化,至少不需要验的区域:Math.min(pArr[2 * index - i], pR - i)
pArr[i] = pR > i ? Math.min(pArr[2 * index - i], pR - i) : 1;
// 第二句代码:在i位置尝试往外扩最长回文半径长度pArr[i]:
// 如果扩成功pArr[i]++;否则立刻停止扩的过程break
while (i + pArr[i] < cs.length && i - pArr[i] >= 0) {
if (cs[i + pArr[i]] == cs[i - pArr[i]])
pArr[i]++;
else {
break;
}
}
// 每轮循环,扩的长度超过回文右边界下一个位置,就更新pR和index
if (i + pArr[i] > pR) {
pR = i + pArr[i];
index = i;
}
// 取pArr[i]中最大值
maxLen = Math.max(maxLen, pArr[i]);
}
// 最长回文串长度是回文半径-1,比如#1#2#1#,2的最长回文半径是4,所以原始串121的长度是4-1=3
return maxLen - 1;
}
// 将str转换成带#号的字符数组:解决奇数、偶数中心往外扩的差异性
public static char[] manacherString(String s, int n) {
char[] charArr = s.toCharArray();
int index = 0;// index遍历charArr
// s:a -> res:#a#,长度1 -> 3,奇数位放#,偶数位放原字符
// s:ab -> res:#a#b#,长度2 -> 5,奇数位放#,偶数位放原字符
// s:aba -> res:#a#b#a#,长度3 -> 7,奇数位放#,偶数位放原字符
// 长度变化规律:len -> len+len+1=len*2+1,奇数位放#,偶数位放原字符
char[] res = new char[n * 2 + 1];
for (int i = 0; i != res.length; i++) {
// 偶数位放#,奇数位放原字符
res[i] = (i & 1) == 0 ? '#' : charArr[index++];
}
return res;
}
} 
京公网安备 11010502036488号