题目详情
题目链接:https://leetcode-cn.com/problems/longest-palindromic-substring/
初次完成
老规矩,确保自己的第一思路能先完成功能。反正是各种运行调试,有问题就debug
public String longestPalindrome(String s)
{
String maxSubstr = "";
int in;
a:for (int i = 0; i < s.length(); i++)
{
String s1 = s.substring(i);
char ch = s.charAt(i);
b:while ((in = s1.lastIndexOf(ch)) != -1)
{
s1 = s1.substring(0,in+1);
for (int n = 0; n < s1.length()/2; n++)
{
if (s1.charAt(n) != s1.charAt(s1.length()-n-1))
{
s1 = s1.substring(0,s1.length()-1);
continue b;
}
}
maxSubstr = maxSubstr.length()>s1.length()?maxSubstr:s1;
continue a;
}
}
return maxSubstr;
}
感觉能想到的都是最笨的办法,完全没有什么利用算法的思想,幸好没给我说什么超时,不过这效率是真的低
思路就是:先定义一个最长的回文串为空字符,从头遍历这个字符串,每个字符,都从后面去找和它相同的字符,如果找到了相同的字符,再往中间判断是否为回文串,是就比较更新最长回文串,不是就再找下一个相同的字符……处理好各种边界条件就OK。
首次优化
关于优化,我首先想到的就是,我第一次提交成功的代码里面,每层循环里使用了很多的字符串截取操作,每遍历一个字符,下一次遍历我都将它截取为一个新的字符串。我觉得字符串的截取,应该挺浪费时间和空间的,所以我决定用StringBuilder来替换有字符串截取的地方。
将能替换的地方都替换为StringBuilder之后(有的地方无法替换),第二轮代码出来了:
public String longestPalindrome(String s)
{
StringBuilder st = new StringBuilder("");
int in;
a:for (int i = 0; i < s.length(); i++)
{
String s1 = s.substring(i);
char ch = s.charAt(i);
b:while ((in = s1.lastIndexOf(ch)) != -1)
{
s1 = s1.substring(0,in+1);
int length = s1.length();
for (int n = 0; n < length/2; n++)
{
if (s1.charAt(n) != s1.charAt(length-n-1))
{
s1 = s1.substring(0,length-1);
continue b;
}
}
st = st.length()>s1.length()?st:new StringBuilder(s1);
continue a;
}
}
return st.toString();
}
然而效果并没很理想:
效率方面有点提升,内存方面还是和原理差不多,我不是将String原生的截取方法换成了StringBuilder吗?按道理内存方面应该会更优秀一些的,这是为什么呢?难道是因为多次创建StringBuilder对象的原因?
再次优化
这次我就在想:我为什么要进行字符串截取操作呢?这些所有的东西,不都可以用字符串的下标来完成吗?只需要最后在返回最长回文子串的时候截取一次不就行了吗?
又开始改了,主要思路不变,只不过全都改成了对字符串索引进行操作
public String longestPalindrome(String s)
{
int l;
if (s == null || (l =s.length()) < 1) return "";
int a = 0,b = 0,c;
a:for (int i = 0; i < l; i++)
{
int e = l-1;
char f = s.charAt(i);
b:while ((c = s.lastIndexOf(f,e)) != -1 && c != i)
{
e = c-1;
for (int n = i,m=0; m < (c-i)/2; n++,m++)
{
if (s.charAt(n+1) != s.charAt(c-1-m))
{ continue b; }
}
if (b - a < c - i)
{ a = i;b = c; }
continue a;
}
}
return s.substring(a,b+1);
}
这次有那么一丢丢的进步,不过总体来说还是不是很理想
我觉得这已经是我这个思路的极限了,想要再提升,就应该从优化思路考虑了。
大佬思路:
这题的解法,大致有四种:暴力算法、动态规划、中心扩散、Manacher 算法。
我上面的应该就是暴力算法了,思路清晰,好理解,但是缺点就是效率比较低。
动态规划和中心扩散,作为面试者,一定要掌握。至于Manacher 算法,当扩宽视野就好,鉴于目前的水平,也就不拓宽了。就动态规划和中心扩散吃透就好。
动态规划
没系统的学过这个算法,有点烧脑,借大佬的一张图:
主要思路就是:后一个情况的状态是由前面某个情况的状态确定的。具体的细节可以去看这位大佬写的题解:大佬题解,上面这张图片就是在那里保存的。
看了大佬的思路和代码,我自己再写了一遍:
public String longestPalindrome(String s)
{
//特判
int length;
if (s == null || (length = s.length()) < 2)
{ return s; }
int start = 0;
int end = 0;
boolean[][] dp = new boolean[length][length];
char[] chars = s.toCharArray();
//对角线肯定为true
for (int i = 0; i < length; i++)
{ dp[i][i] = true; }
for (int j = 1; j < length; j++)
{
for (int i = 0; i < j; i++)
{
if (chars[i] == chars[j] )
{
if (j- i < 3) { dp[i][j] = true; }
else
{ dp[i][j] = dp[i+1][j-1]; }
}
if (dp[i][j] && (j-i) > (end-start) )
{
start = i;
end = j;
}
}
}
return s.substring(start,end+1);
}
提交结果
果然比我自己写的那个暴力解法效率高了好几倍。
中心扩散
中心扩散的思路就是:遍历字符串,每对每个字符串进行两遍扩散。然后再记录是否为回文串,记录索引,最后返回最长的那个。我觉得和我自己写那个差不多,不同的就是我是从两边往中间判断,而这个思路是从中间往两头判断,代码就省略了。
总结
自己写了一遍之后,对动态规划有了一点认识。我决定将这个动态规划好好练一练,那个图里,大佬总结了哪些题用到了动态规划。