先看一眼题:
我的思路:正反向计算哈希,相同则说明是回文串,相比直接两两字符进行比较,减少了比较的次数。如果 maxLen已经比剩余子串的总长度还长,则可以跳出循环。
需要注意:计算哈希的时候,不要超过long的值。
我定义的哈希函数为:s.charAt(i)*26^k,其中k=0,1,2,3,4.当k为5时,令其等于0,避免太大。
通过哈希来判断一个字符串是不是回文串:
public static boolean hashCalculation(String s) {
long forward = 0, reverse = 0;
//奇数则算[0,len/2]和[len/2,len),偶数则算[0,len/2)和[len/2,len)
int len = s.length();
int i = 0, j = len - 1;
int k = 0;
for (; i < len / 2; i++,k++) {
forward += Math.pow(26, k) * s.charAt(i);
// if (forward > Math.pow(2, 16))
// forward = (long) (forward / Math.pow(20, i));
if(k%5==0)
k=0;
}
if (len % 2 != 0) {
//len为奇数奇数
forward += Math.pow(26, k) * s.charAt(i);
}
for (k = 0; j > len / 2 - 1; j--, k++) {
reverse += Math.pow(26, k) * s.charAt(j);
// if (reverse > Math.pow(2, 16))
// reverse = (long) (reverse / Math.pow(20, i));
if(k%5==0)
k=0;
}
return forward == reverse;
}
然后在此基础上,寻找最长的回文子串
public static String longestPalindrome(String s) {
if (s.length() == 0)
return "";
if (s.length() == 1)
return s;
int i = 1, j = 0;
String sub = s.substring(0, 1);//如果最后没有回文串,那就返回第一个字符,因为第一个字符本身也是回文串
int maxLen = 0;
while (j < s.length()) {
for (; i < s.length(); i++) {
if (s.charAt(j) == s.charAt(i))
break;
}
if (i < s.length() && hashCalculation(s.substring(j, i + 1))) {
// 如果新的回文子串比原来的大,才更新
if (s.substring(j, i + 1).length() > maxLen) {
sub = s.substring(j, i + 1);
maxLen = sub.length();
}
}
i++;
if (i - 1 == s.length()) {
j++;
if (maxLen > s.length() / 2)
break;
i = j + 1;
}
}
return sub;
}
提交!结果显示“超出时间限制”
明明我在自己电脑上跑是没有问题的呀!那么只有一种原因,就是时间复杂度过高了!计算量太大了!
因此在此基础上,加了一个栈:
·思路二: 思路一在我的电脑上可以跑通,但是在Leetcode上会超时,比如测试一千个"a"组成的字符串,比较的次数太多了!因此要进行优化
·优化思路:比较最大的串,如果不是回文串再比较子串,比如测试abcbaea,先比较abcbaea,再比较abcba。
·优化原理:如果大串是回文串,则不考虑其内部的子串
·写的时候发现这样做还可以避免很多i的边界比较问题,进一步提高了效率
修改后的代码如下:
public static boolean hashCalculation(String s) {
long forward = 0, reverse = 0;
//奇数则算[0,len/2]和[len/2,len),偶数则算[0,len/2)和[len/2,len)
int len = s.length();
int i = 0, j = len - 1;
int k = 0;
for (; i < len / 2; i++,k++) {
forward += Math.pow(26, k) * s.charAt(i);
if(k%5==0)
k=0;
}
if (len % 2 != 0) {
//len为奇数奇数
forward += Math.pow(26, k) * s.charAt(i);
}
for (k = 0; j > len / 2 - 1; j--, k++) {
reverse += Math.pow(26, k) * s.charAt(j);
if(k%5==0)
k=0;
}
return forward == reverse;
}
public static String longestPalindrome(String s) {
if (s.length() == 0)
return "";
if (s.length() == 1)
return s;
int i = 1, j = 0;
String sub = s.substring(0, 1);//如果最后没有回文串,那就返回第一个字符,因为第一个字符本身也是回文串
int maxLen = 0;
Stack stack = new Stack();
while (j < s.length()) {
for (; i < s.length(); i++) {
if (s.charAt(j) == s.charAt(i))
stack.add(i);
}
while (!stack.isEmpty()){
int index = (int)stack.pop();
if (hashCalculation(s.substring(j, index + 1)) && s.substring(j, index + 1).length() > maxLen) {
sub = s.substring(j, index + 1);
maxLen = sub.length();
break;
}
}
stack.clear();
j++;
i=j+1;
// 如果 maxLen已经比剩余子串的总长度还长,则可以跳出循环
if(maxLen>s.substring(j,s.length()).length())
break;
}
return sub;
}
提交!还是超出时间限制!!
其实这样改动过后时间复杂度并没有任何变化,只是对于一些特殊的情况而言,变得比原来快一点了。
看来我是聪明反被聪明误了,直接暴力解法进行比较,其实是比计算哈希要快的,本来已经毫无思路了,就试了一下暴力解法,代码如下:
// 事实证明直接比较会比计算哈希要快
public static boolean isPalindrome(String s){
for(int i = 0 , j = s.length()-1 ; i<s.length()/2 ; i++,j--)
if(s.charAt(i)!=s.charAt(j))
return false;
return true;
}
只需要把上面的主程序中的hashCalculation替换为isPalindrome即可运行。
再次提交,成功了!但是速度慢的一批,因为暴力解法是最慢的解法了(我上面俩更慢,hhh)看了一眼答案,用的dp,头有点大,dp的思路明白了,但是dp怎么写成代码,看了一眼还是没懂,今天懒得看了,这里也就不粘贴答案代码了,有兴趣的小伙伴直接上leetcode官网上看吧。