问题
寻找最长回文子串是一个经典的面试编码问题。在这篇文章中,将为这个问题总结3种不同的解决方案。
解决方案
1. 朴素方法
我们可以简单地检查每个子字符串并检查它是否是回文的。时间复杂度为O(n3)。如果这是提交给LeetCode onlinejudge,将返回一个错误消息“时间限制超过”。因此,这种方法只是一个开始,我们需要一个更好的算法。
public static String longestPalindrome1(String s) {
int maxPalinLength = 0;
String longestPalindrome = null;
int length = s.length();
// 遍历所有可能的子串
for (int i = 0; i < length; i++) {
for (int j = i + 1; j < length; j++) {
int len = j - i + 1;
String curr = s.substring(i, j + 1);
if (isPalindrome(curr)) {
if (len > maxPalinLength) {
longestPalindrome = curr;
maxPalinLength = len;
}
}
}
}
return longestPalindrome;
}
public static boolean isPalindrome(String s) {
for (int i = 0; i < s.length() - 1; i++) {
if (s.charAt(i) != s.charAt(s.length() - 1 - i)) {
return false;
}
}
return true;
}
2. 动态规划法
假设s是输入字符串,i和j是字符串的两个下标。定义一个二维数组“table”,让table[i] [j]表示从i到j的子串是否为回文。
初始情况:
table[i] [i] == 1;
table[i] [i+1] == 1 => s.charAt(i) == s.charAt(i+1)
变化条件:
table[i+1] [j-1] == 1 && s.charAt(i) == s.charAt(j)=>table[i] [j] == 1
时间O(nˆ2),空间O(nˆ2)。
public static String longestPalindrome2(String s) {
if (s == null || s.length() <= 1) {
return s;
}
int maxLen = 0;
String longestStr = null;
int length = s.length();
int[][] table = new int[length][length];
// 初始情况 对角线均为1 即单个字符是回文的
for (int i = 0; i < length; i++) {
table[i][i] = 1;
}
printTable(table);
// 考虑回文长度为2
for (int i = 0; i < length - 2; i++) {
if (s.charAt(i) == s.charAt(i + 1)) {
table[i][i + 1] = 1;
longestStr = s.substring(i, i + 2);
}
}
printTable(table);
// 计算回文长度为3-length的情况
for (int l = 3; l <= length; l++) {
for (int i = 0; i <= length - l; i++) {
int j = i + l - 1;
if (s.charAt(i) == s.charAt(j)) {
table[i][j] = table[i + 1][j - 1];
if (table[i][j] == 1 && l > maxLen) {
longestStr = s.substring(i, j + 1);
} else {
table[i][j] = 0;
}
printTable(table);
}
}
}
return longestStr;
}
public static void printTable(int[][] x) {
for (int[] y : x) {
for (int z : y) {
System.out.print(z + " ");
}
System.out.println();
}
System.out.println("-------");
}
给定一个输入,我们可以使用printTable方法在每次迭代后检查这个表。例如,如果输入字符串是“dabcba”,那么最终的矩阵将如下所示:
1, 0, 0, 0, 0, 0
0, 1, 0, 0, 0, 1
0, 0, 1, 0, 1, 0
0, 0, 0, 1, 0, 0
0, 0, 0, 0, 1, 0
0, 0, 0, 0, 0, 1
从表中,我们可以清楚地看到,最长的字符串在单元格table[1] [5]中。
3. 更简单的算法
时间O(n2),空间O(1)。
public static String longestPalindrome3(String s) {
if (s.isEmpty() || s.length() == 1) {
return s;
}
String longest = s.substring(0, 1);
for (int i = 0; i < s.length(); i++) {
// 以一个字符为中心点的回文传
String tmp = helper(s, i, i);
if (tmp.length() > longest.length()) {
longest = tmp;
}
// 以两个字符为中心点的回文传
tmp = helper(s, i, i + 1);
if (tmp.length() > longest.length()) {
longest = tmp;
}
}
return longest;
}
// 给一个中心点,一个或两个字符,找到以它为中心的最长回文子串
public static String helper(String s, int begin, int end) {
while (begin >= 0 && end <= s.length() - 1 && s.charAt(begin) == s.charAt(end)) {
begin--;
end++;
}
return s.substring(begin + 1, end);
}
4. 测试代码
public static void main(String[] args) {
String s = "abcdcbo";
System.out.println(longestPalindrome1(s));
String s2 = "dabcba";
System.out.println(longestPalindrome2(s2));
String s3 = "dabcba";
System.out.println(longestPalindrome3(s3));
}
测试结果:
bcdcb
1 0 0 0 0 0
0 1 0 0 0 0
0 0 1 0 0 0
0 0 0 1 0 0
0 0 0 0 1 0
0 0 0 0 0 1
-------
1 0 0 0 0 0
0 1 0 0 0 0
0 0 1 0 0 0
0 0 0 1 0 0
0 0 0 0 1 0
0 0 0 0 0 1
-------
1 0 0 0 0 0
0 1 0 0 0 0
0 0 1 0 1 0
0 0 0 1 0 0
0 0 0 0 1 0
0 0 0 0 0 1
-------
1 0 0 0 0 0
0 1 0 0 0 1
0 0 1 0 1 0
0 0 0 1 0 0
0 0 0 0 1 0
0 0 0 0 0 1
-------
abcba
abcba
进程已结束,退出代码 0