近日要找实习,需要练习一下算法,于是准备刷题。用的是leetcode。但是刷了一段时间,就发现之前自己之前刷的题目的思路不是很清晰了,于是在这里记录一下自己的思路和代码,以供之后复习理解。
第五题Longest Palindromic Substring
第五题Longest Palindromic Substring
题干:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example:
Input: “babad”
Output: “bab”
Note: “aba” is also a valid answer.
Example:
Input: “cbbd”
Output: “bb”
思路:
看到题目,我首先就想到了用栈去解决,后来想如果还有,中间推出去的栈的元素就没有用了。然后就老老实实的从中间往两边找相等元素。这样的话,就要考虑两种情况一种,中间只有一个元素自己自身回文,中间有两个元素相等表示回文。思路基本如此,而为了减少判断次数,我的边界条件加了限制。下面直接上代码:
char* longestPalindrome(char* s) {
if(s[0] == '\0')return NULL;
int tmplen = 1;
int maxlen = 1;
int maxindex = 0;
int tmpindex = 0;
int slen = strlen(s);
for(int i = 0; i < (slen-maxlen/2); i++){//减少判断次数
int j = i-1;
int k = i+1;
while(j>=0 && (k<=slen&&s[j]==s[k])){//中间一个元素自己回文
j--;k++;tmplen+=2;
}
tmpindex = j+1;
if(s[i] == s[i+1]){//两个元素回文
int j2 = i-1;
int k2 = i+2;
int tmplen2 =2;
while(j2>=0 && (k2<=slen&&s[j2]==s[k2])){
j2--;k2++;tmplen2+=2;
}
if(tmplen2 > tmplen){
tmplen = tmplen2;
tmpindex = j2+1;
}
}
if(tmplen > maxlen){
maxlen = tmplen;
maxindex = tmpindex;
}
tmplen = 1;
//printf("maxlen = %d\n",maxlen);
}
printf("maxlen = %d,maxindex =%d\n",maxlen,maxindex);
char *ret = (char *)malloc(sizeof(char)*1001);
for(int i = 0; i < maxlen; i++){
ret[i] = s[maxindex+i];
}
ret[maxlen]='\0';
return ret;
}
找工作好艰难,还是一道一道刷下来吧!!!
意涵团加油!!!
2017年3月30日再次更新
后来又思考了一下,感觉如果只是加边界减少判断次数对于提高效率改善并不大。因为对于每个回文中点开始寻找回文的时候,都是从中心往外扩散,即tmplen总是从0开始扩展的。但是对于每个中点的下一个看起来总是和之前没有什么关系的。于是我举了一个特殊的例子。
s[9]=ababababa;
p[9]=123454321;
在这里p数组存的是以第i个位置为中心可以回文扩展的最长扩展长度(这个长度可以确定回文长度),如s[2]为中,可以得到最长回文为ababa,从s[2]出发,可以向左/右扩展的最长长度为3。在这里我们设置一个坐标值rightbounder(简称为rb)。当寻找完中心为s[2]的p[2]值后的rb=2+p[2]-1。对于一般情况,rb=i+p[i]-1。可以从上面例子去验证。从中我们可以发现s[4]为中心的时候 p[4]=5,即rb指向了最后一个元素。即这个扩展域覆盖了整个数组,而有意思的是关于p[4]对称的值,如p[3],p[5]值相等。从这个例子可以得到一个启发,在当前寻找完的回文范围(必须在这个范围内)之内,其左右位置的p值是对称的。比如在上面例子里最后五个字符ababa(中心为s[6])其得到的也是对称的,但是在整个字符串中缺不是对称的。
实际上这种情况就是中心左边的得到的回文覆盖范围超过了本中心的覆盖范围,而对称到右边超出其rb的位置暂时也不知道是不是可以往后扩展。所以在这里限制下一个中心扩展初始范围的是右边界。而当左边的回文覆盖范围没有超出中心覆盖范围的情况下,其右边对称的初始扩展则受到了这样的限制。
那么当准备扩展中心等于或超出rb时,则只能从1开始扩展了。
上面说得十分详细,主要是我思考的过程,而总结起来就是,通过回文对称性,使得新中心的扩展初始值不是从1开始,即减少了无谓的扩展次数。下面我们再考虑一下如果中心字符不是一个,而是一对怎么办?
实际上,我们只要给字符串进行处理便可以将中心字符串变为一个。举个例子:
s[i]=abba;
modifys[i]=#a#b#b#a#;
只要最后找到modifys的p值与真正回文的长度关系即可。
下面来一个求长度的代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define min(a,b) a>b?b:a
int longestsub(char *s);
char *pretest(char *s ,int slen);
int main(){
int n;
char word[1000001];
scanf("%d",&n);
while(n > 0){
scanf("%s",word);
printf("%d\n",longestsub(word));
n--;
}
return 0;
}
int longestsub(char *s){
if(s[0] == '\0')return 0;
int slen = strlen(s);
char *modifys = malloc(sizeof(char)*(2*slen+1));
modifys = pretest(s,slen);
int i;
int id = 0;
int maxl = 0;
int maxlen =0;
int p[2*slen+1];
p[0] = 0;
for(i = 1; i < 2*slen+1; i++){
p[i] = maxl > i ? min(p[2*id-i],maxl-i) : 1;
while(modifys[i-p[i]] == modifys[i+p[i]] &&(i-p[i]>=0 && i+p[i] < 2*slen+1))p[i]++;
maxlen =( maxlen >= p[i]) ? maxlen:p[i];
if(i+p[i] > maxl){
maxl = i+p[i];
id = i;
}
}
return maxlen-1;
}
char *pretest(char *s,int slen){
char *ret = (char *)malloc(sizeof(char)*(2*slen+1));
int i;
for(i = 0; i<2*slen+1; i++){
if(i%2 == 0)ret[i]='#';
else{
ret[i]=s[i/2];
}
}
ret[2*slen+1]='\0';
return ret;
}