LeetCode第五题:寻找最长回文子串
LeetCode第五题:
给定一个字符串
s
,找到s
中最长的回文子串。你可以假设s
的最大长度为 1000。
示例 1:
输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。
示例 2:
输入: "cbbd"
输出: "bb"
这道题做了是真的久。其实想想并不难。一开始的时候是算法完全错了,进入了思维的误区。一直在debug,不断测试,然后都是部分测试可以通过,部分就是不通过。以为自己只是没有考虑到一些边界问题,然后每改一次,就多通过一些,但是任然有的会错误。到第二天依然不行。中文睡午觉得时候,没睡着,一直在脑中复现,突然就发现是我写的算法本身的问题。 然后就开始想别的算法。想到了一个递归算法,这回没错,但是当字符串很长的时候,就会栈溢出,不能满足题目需要。然后就把递归算法改成了普通循环,这回又变成了超时。真的是在做这题的时候什么都遇到了。分析了一下时间复杂的,时间复杂度是
n^3
。最后是看了分析,最后写了本文最后的算法,时间复杂的n^2
.
第一次尝试
一开始没有仔细想,就开始做了。于是就想错了。算法如下:
算法一:
class Solution {
public String longestPalindrome(String s) {
//子串长度
int slength = 0;
//长串移位
int move = 0;
//长串长度
int l = s.length();
//字符数组个数
int m = 0;
//选定的数组序号
int n = 0;
//记录长度
int t = 0;
char[][] stringChar = new char[1000][l];
char[] chars = s.toCharArray();
int j = l - 1;
while (slength <= l - move) {
//组内循环号
int k = 0;
boolean flag = false;
for (int i = 0; i < l && j >= 0; i++) {
if (chars[i] == chars[j]) {
stringChar[m][k] = chars[i];
flag = true;
k++;
j--;
continue;
}
if (flag && k > t) {
int temp = n;
n = m;
t = k ;
m=temp;
// m++;
k = 0;
j = l-move-1;
flag = false;
}
if (flag) {
j = l-move-1;
// m++;
flag = false;
}
}
if (flag && k > t) {
n = m;
t = k ;
}
slength = t;
move++;
j = l-move-1;
if (j<=0) break;
}
StringBuffer sb = new StringBuffer();
for (int i = 0;i<t;i++){
sb.append(stringChar[n][i]);
}
return sb.toString();
}
}
主要的思想是把该字符串逆转,想想有两个指针,一开始分别置于正字符串和反字符串的头上。
第一次
cbada
adabc
第二次
cbada
adabc
第三次
cbada
adabc
如果不匹配,则正字符串的指针移位,反字符串不动。如匹配,则正反同时移位,直到不匹配,将匹配成功的子字符串与一开始的比较,取长者。
总之这个过程就是不断的移位,匹配的过程。可以看到,代码中也是写了很多的期指变量,看着难受,写着也头疼。花费了大量的时间解决边界问题,但是最重要的是,该问题不能被这样解决!如下:
tabgat
tagbst
可以看tabgat到,字串
tabgat
的最大回文字串就是单个字符。但是使用算法一,就会被判断为最长回文子串为ta
,所以该算法根本不可行。
第二次尝试
还是躺着比较容易思考.
否决了之前的算法后,一切都舒畅多了。突然想到,寻找最长子串可以用递归的思路来想。
当一个问题可以分解为类型相同,但规模不同的子问题的时候,且有最终状态,就可以用递归解决。
- 寻找最字符串
s
的最长回文串,找到则返回。 - 寻找比
s
的长度短1的字符串的最长回文串 - 当字符串为空或长度为一时,则直接返回。
这里我也遇到了一个问题,那就是在第二步寻找子问题的时候。因为比原串s
短1的子串有两个,去头或去尾。然后一开始写出了错误的递归算法:
class Solution {
public String longestPalindrome(String s) {
if (isPalinedrome(s)) return s;
String s1 = s.substring(0,s.length()-1);
String s2 = s.substring(1,s.length());
return findNext(s1,s2);
}
//判断是否回文
boolean isPalinedrome(String s) {
if ("".equals(s)) return true;
char[] chars = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (i >= s.length() - i -1) return true;
if (chars[i] != chars[s.length() - i - 1]) return false;
}
return false;
}
//找到下一个子串
String findNext(String s1, String s2) {
if (isPalinedrome(s1)) return s1;
if (isPalinedrome(s2)) return s2;
return findNext(s1.substring(0,s1.length()-1),s2.substring(1,s2.length()));
}
}
看起来好像情况都考虑了,实则没有。在第20行这里。会一直执行,直到返回回文,或到最后返回单个字符。s2永远不会被考虑。
后来我就想到了如下的算法:
class Solution {
public String longestPalindrome(String s) {
return findNext(s, s.length(), 0);
}
//判断是否回文
boolean isPalinedrome(String s) {
if ("".equals(s)) return true;
char[] chars = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (i >= s.length() - i -1) return true;
if (chars[i] != chars[s.length() - i - 1]) return false;
}
return false;
}
//找到下一个子串 s母串,n子串长度,index子串序号
String findNext(String s, int n, int index) {
if (isPalinedrome(s.substring(index, index + n))) return s.substring(index, index + n);
else {
if (index < s.length() - n) index++;
else {
n--;
index = 0;
}
}
return findNext(s, n, index);
}
}
依次寻找下一个字符串。可行。然后使用
for
循环,改成了非递归的方式:
class Solution {
public String longestPalindrome(String s) {
return findNext(s, s.length(), 0);
}
//判断是否回文
boolean isPalinedrome(String s) {
if ("".equals(s)) return true;
char[] chars = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (i >= s.length() - i -1) return true;
if (chars[i] != chars[s.length() - i - 1]) return false;
}
return false;
}
//找到下一个子串 s母串,n子串长度,index子串序号
String findNext(String s, int n, int index) {
while (!isPalinedrome(s.substring(index, index + n))) {
if (index < s.length() - n) index++;
else {
n--;
index = 0;
}
}
return s.substring(index, index + n);
}
}
依次验证每一个子串是不是回文。子串共有(1+n)*n/2个,每个子串验证回文时间复杂度为n,所以时间复杂的为n^3。在LeetCode验证超时。
今天写了最终版。分别以每个字符或俩字符间隙为中心,从外扩散。找到以之为中心的最长回文,与原来的比较,取长者,最终返回。时间复杂度为n^2.
class Solution {
public String longestPalindrome(String s) {
String palindrome = "";
char[] chars = s.toCharArray();
for (int i = 0; i < (2 * s.length())-1; i++) {
int big = 0;
while (i + big < ((2 * s.length())-1) && i - big >= 0 && (chars[(i - big)/2] == chars[(i + big)/2])) {
if (i % 2 != 0) {
if (big == 0) {big++;}
else if (chars[(i - big)/2] == chars[(i + big)/2]) {
big = big + 2;
}
}
else {
if (chars[(i - big)/2] == chars[(i + big)/2]) {
big = big + 2;
}
}
}
big = big - 2;
if (big >= palindrome.length()) {
palindrome = s.substring((i - big) / 2, (i + big) / 2+1);
}
}
return palindrome;
}
}
小结
这道题我真的遇到了好几种问题,也写了好久。最大的感受就是其实只要好好想清楚了,写起来就是一会的事。一旦陷入思维的误区,还不走出来,就会万劫不复。动手前,真的要好好想清楚思路,不要太着急。