思路
这道题没什么思路, 于是我首先尝试了最简单的方式 -- BF. 也就是两个for嵌套, 第一个for确定首字母, 第二个for确定长度, 然后循环判断确定出来的字符串是否是回文字符串. 不出意外, TLE...
public class Solution {
public String longestPalindrome(String s) {
if(s.isEmpty()) return "";
String re = s.substring(0, 1);
for(int startCharIndex = 0; startCharIndex < s.length() - 1; ++startCharIndex){
for(int length = 2; startCharIndex + length - 1 < s.length(); ++length){
if(isPalindromic(s, startCharIndex, startCharIndex + length - 1)){
if(length > re.length()){
re = s.substring(startCharIndex, startCharIndex + length);
}
}
}
}
return re;
}
private boolean isPalindromic(String s, int front, int back){
while(front < back){
if(s.charAt(front++) != s.charAt(back--)){
return false;
}
}
return true;
}
}
这时候仍然没有什么颠覆性思路, 但是想到了两条可以优化上面的解法 :
- 第二个for对长度的遍历可以由长到短, 这样一旦长的已经是回文字符串的话就可以不用考虑短的, 可以直接break出去.
- 如果此时遍历的字符串的长度已经不大于当前保存的回文字符串的长度, 那么此时已经无需检查这个字符串, 也可以直接break出去.
基于这两条我对我上面的解法稍微优化了一下, 居然AC了, 当然时间排名就很靠后了...
public class Solution {
public String longestPalindrome(String s) {
if(s.isEmpty()) return "";
String re = s.substring(0, 1);
for(int startCharIndex = 0; startCharIndex < s.length() - 1; ++startCharIndex){
for(int length = s.length() - startCharIndex; length > 1; --length){
if(re.length() >= length) break;
if(isPalindromic(s, startCharIndex, startCharIndex + length - 1)){
if(length > re.length()){
re = s.substring(startCharIndex, startCharIndex + length);
break;
}
}
}
}
return re;
}
private boolean isPalindromic(String s, int front, int back){
while(front < back){
if(s.charAt(front++) != s.charAt(back--)){
return false;
}
}
return true;
}
}
这两种解法都没什么思考量, 具体实现也没什么难度. 经过一天的研究, 我发现了一种O(n^2)的解法 :
大概思路是这样的 :
- 维护一个二维数组map, i表示字符串的开始位置, j表示字符串的结束位置, 然后map[i][j]表示字符串s.subString(i, j + 1)是否为回文字符串, 如果是则为true, 否则为false.
- 然后由于回文字符串有两种形式 : 如果长度为奇数, 那么它绝对是以某个字母为中心; 如果为偶数, 则是两边完全相同. 这样的话, 回文字符串的中心位置要么只有一个字母, 然后两边都相同. 要么有两个相同的字母.
- 那么我们完全可以分离这两种情况, 分别从一个字母和两个字母开始, 如果他们是回文字符串的话, 我们检查他们的左右两边, 如果相同的话, 说明这也是回文字符串, 这样当整个表被填满的时候我们也就遍历了所有的情况. 这样写AC了, 但是时间排名仍然很低, 具体实现是这样的 :
public class Solution {
public String longestPalindrome(String s) {
if(s.isEmpty()) return "";
int left = 0;
int right = 0;
boolean[][] map = new boolean[s.length() - 1][s.length()];
for(int i = 0; i < s.length() - 1; ++i){
int j = 1;
while(i - j > -1 && i + j < s.length()){
map[i-j][i+j] = s.charAt(i - j) == s.charAt(i + j);
if(!map[i-j][i+j]) break;
else if(right - left < 2 * j){
left = i - j;
right = i + j;
}
++j;
}
map[i][i + 1] = s.charAt(i) == s.charAt(i + 1);
if(map[i][i+1]){
if(right - left < 1){
left = i;
right = i + 1;
}
j = 1;
while(i - j > -1 && i + 1 + j < s.length()){
map[i-j][i+1+j] = s.charAt(i - j) == s.charAt(i + 1 + j);
if(!map[i-j][i+1+j]) break;
else if(right - left < 1 + 2 * j){
left = i - j;
right = i + 1 + j;
}
++j;
}
}
}
return s.substring(left, right + 1);
}
}
后面在网上看到更简单的, 虽然也是O(n^2), 但是却不需要维护一个二维数组. 大概思路就是, 我上面提到了回文字符串的中心, 实际上回文串的中心只有2n-1种情况(n种在字母上, n - 1中在两字母中间), 那么我们只需要分别考虑这2n-1种情况即可, 这其实是对于我上面这种情况的优化.
public class Solution {
public static void main(String[] args) {
System.out.println(new Solution().longestPalindrome("cbbd"));
}
public String longestPalindrome(String s) {
String re = null;
for(int i = 0; i < 2 * s.length() - 1; ++i){
String temp = findMaxPalindromic(s, i);
if(re == null || re.length() < temp.length()){
re = temp;
}
}
return re;
}
private String findMaxPalindromic(String s, int mid){
if(mid % 2 == 0){
int index = mid / 2;
int length = 0;
while(index - length > -1 && index + length < s.length() && s.charAt(index - length) == s.charAt(index + length)) ++length;
return s.substring(index - length + 1, index + length);
}
else{
int left = (mid - 1) / 2;
int right = (mid + 1) / 2;
int length = 0;
while (left - length > -1 && right + length < s.length() && s.charAt(left - length) == s.charAt(right + length)) ++length;
return s.substring(left - length + 1, right + length);
}
}
}
这个时候时间排名差不多40%, 这说明仍然有更快的方法存在. 不过我觉得这种方式已经够了...