目录
[解法5: Manacher算法](#解法5: Manacher算法)
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
解法1:递归算法
1)判断输入的字符串是否是回文,若是肯定为最大回文串。或者字符串长度<=1 直接返回。(结束条件)
- 拆分问题降级。
若字符串不是回文,那么只能在s[0,n-2] 和 s[1,n-1]两个子串里去找最大回文串。
然后选取长度更大的那个返回结果即是结果。
方法很简单,代码如下:
java
class Solution {
public String longestPalindrome(String s) {
if (isPalindrome(s)) {
return s;
}
String left, right;
int n = s.length();
// find palindrome in left part
left = longestPalindrome(s.substring(0, n-1));
// find palindrome in right part
right = longestPalindrome(s.substring(1, n));
if (left.length() > right.length()) {
return left;
} else {
return right;
}
}
public boolean isPalindrome(String s) {
char[] schars = s.toCharArray();
int n = schars.length;
for (int i=0; i<n; i++) {
if (schars[i] != schars[n-1-i]) {
return false;
}
}
return true;
}
}
前面都能跑过,碰到测试用例:"babaddtattarrattatddetartrateedredividerb" 运行超时。
这个递归,很多重复计算,效率比较低。那就优化下,来个高效率点的吧。
解法2:Map取同字母位置法
我们知道每一个回文串,它的开头和结尾两个字母肯定相同。那么我们可以得到一个灵感,把所有字母的字符串中位置记录下来,回文只可能在那些出现了两次以上位置的字母里出现。
例如:"abcabcde"
Map [Character, List<Integer>]
a => [0, 3]
b => [1, 4]
c => [2, 5]
d => [6]
e => [7]
那么我们从字母的位移列表中,选出超过2个的进行检查所有的字符串是否为回文,然后取出最大的就可以了。
代码如下:
java
class Solution {
public String longestPalindrome(String s) {
if (isPalindrome(s)) {
return s;
}
// letter <-> count & index list
Map<Character, List<Integer>> letterMap = new HashMap<>();
char[] schars = s.toCharArray();
for (int i=0; i<schars.length; i++) {
Character c = Character.valueOf(schars[i]);
List<Integer> indexs = letterMap.get(c);
if (indexs != null) {
indexs.add(Integer.valueOf(i));
} else {
indexs = new LinkedList<>();
indexs.add(Integer.valueOf(i));
letterMap.put(c, indexs);
}
}
String maxRome = "" +schars[0];
for (Map.Entry<Character,List<Integer>> entry: letterMap.entrySet()) {
// more than one index position, maybe there is a palindrome
List<Integer> indexList = entry.getValue();
int indexSize = indexList.size();
if (indexSize > 1) {
for (int i=0; i<indexSize; i++) {
for (int j=indexSize-1; j>i; j--) {
int start = indexList.get(i);
int end = indexList.get(j) + 1;
// sub-string size is less than maxRome, skip it
if (end-start <= maxRome.length()) {
continue;
} else {
String subStr = s.substring(start,end);
if (isPalindrome(subStr) && subStr.length() > maxRome.length()) {
maxRome = subStr;
}
}
}
}
}
}
return maxRome;
}
public boolean isPalindrome(String s) {
int n = s.length();
for (int i=0; i<n; i++) {
if (s.charAt(i) != s.charAt(n-1-i)) {
return false;
}
}
return true;
}
}
运行效果:
虽然过了,但是5%的分布,效果不怎么样。稍微优化下。
1)代码中用的LinkedList来进行记录相同字母的位移下标,在频繁get的时候效率不如ArrayList高。
2)字母Map初始化的时候没有给定初始值,那么会导致resize占用时间。那么我们给定一个size*4/3作为它的初始值。
优化结果代码如下:
java
class Solution {
public String longestPalindrome(String s) {
char[] schars = s.toCharArray();
if (isPalindrome(schars, 0, s.length()-1)) {
return s;
}
// letter <-> count & index list
Map<Character, List<Integer>> letterMap = new HashMap<>(s.length()*4/3);
for (int i=0; i<schars.length; i++) {
Character c = Character.valueOf(schars[i]);
List<Integer> indexs = letterMap.get(c);
if (indexs != null) {
indexs.add(Integer.valueOf(i));
} else {
indexs = new ArrayList<>();
indexs.add(Integer.valueOf(i));
letterMap.put(c, indexs);
}
}
String maxRome = "" +schars[0];
for (Map.Entry<Character,List<Integer>> entry: letterMap.entrySet()) {
// more than one index position, maybe there is a palindrome
List<Integer> indexList = entry.getValue();
int indexSize = indexList.size();
if (indexSize > 1) {
for (int i=0; i<indexSize; i++) {
for (int j=indexSize-1; j>i; j--) {
int start = indexList.get(i);
int end = indexList.get(j);
int subStrLen = end-start+1;
// sub-string size is less than maxRome, skip it
if (subStrLen <= maxRome.length()) {
break;
} else {
String subStr = s.substring(start,end+1);
if (isPalindrome(schars, start, end)) {
maxRome = s.copyValueOf(schars, start, subStrLen);
}
}
}
}
}
}
return maxRome;
}
public boolean isPalindrome(char[] chars, int start, int end) {
while(start < end) {
if (chars[start++] != chars[end--]) {
return false;
}
}
return true;
}
}
经过上面两个代码优化后,执行效果如下:
执行结果位于57%位置,还凑合吧。
解法3:中心扩展法
实现代码如下:
java
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
if (length < 2) {
return s;
}
char[] schars = s.toCharArray();
String maxRome = "" +schars[0];
// search max palindrome from the middle point schars[i]
// palindrome middle from (0, len-1]
for (int i=1; i<length; i++) {
// find max even palindrome from middle point
maxRome = search(schars, i-1, i, maxRome);
// find max odd palindrome from middle point
maxRome = search(schars, i-1, i+1, maxRome);
}
return maxRome;
}
public String search(char[] schars, int left, int right, String maxRome) {
int length = schars.length;
while(left >= 0 && right<length && schars[left] == schars[right]) {
left--;
right++;
}
if (right - left -1 > maxRome.length()) {
return String.copyValueOf(schars, left + 1, right - left -1);
}
return maxRome;
}
}
执行效果如下
93.2%分位,已经很好了。强烈推荐解法。
解法4:动态规划法
理解起来稍微复杂点,可以作为学习动态规划算法的例子。
实现代码如下:
java
class Solution {
public String longestPalindrome(String s) {
int length = s.length();
if (length < 2) {
return s;
}
// palindrome[i][j] = true => s[i,j] is palindrome
boolean[][] palindrome = new boolean[length][length];
// len = 1, all is palindrome
for (int i=0; i<length; i++) {
palindrome[i][i] = true;
}
int start = 0, maxLen = 1;
char[] schars = s.toCharArray();
// palindrome[i][j] = palindrome[i+1][j-1] && s[i]==s[j]
for (int len = 2; len<=length; len++) {
for (int i=0; i<length; i++) {
// j - i + 1 = len
int j = i + len -1;
if (j>length-1) {
break;
}
if (schars[i] == schars[j]) {
if (len <= 3) {
palindrome[i][j] = true;
} else {
palindrome[i][j] = palindrome[i+1][j-1];
}
} else {
palindrome[i][j] = false;
}
if (palindrome[i][j] && len > maxLen) {
start = i;
maxLen = len;
}
}
}
return String.copyValueOf(schars, start, maxLen);
}
}
运行效果如下:
19.07%还凑合吧。
解法5: Manacher算法
还有个Manacher算法比较复杂,效果也没见得比"中心扩展法"好太多,就不推荐了。
感兴趣的童鞋可以去这里看
. - 力扣(LeetCode)