一个字符串的子串是字符串中连续的一个序列,而一个字符串的子序列是字符串中保持相对位置的字符序列,譬如,"adi"可以使字符串"abcdefghi"的子序列但不是子串。这也就决定了在解这两种"LCS"问题上的一些区别。
Longest-Common-Substring和Longest-Common-Subsequence是不一样的。
之前我写的时候这两个概念有错误,见谅。
把子序列改成字串后条件更严苛了,某些情况下解题的复杂度也低一些。
回文子串
比如drabfbaz里面abfba就是回文的,长度为5.
思路1:逐个字母判断
我们以每个字母为中心,逐个判断字符两边的回文长度。
由于比较简单,就不多说了:
//solution1: make i as center and extends
int solution1(char* s)
{
int n=strlen(s);
int i=0,j=0;
int curmax=0,max=0;
for(i=0;i<n;i++)
{
for(j=0;i-j>=0&&i+j<n;j++)//j is the length of substr
{
//odd length
if(s[i+j]!=s[i-j])
break;
curmax=j*2+1;
}
if(curmax>max)
max=curmax;
for(j=0;i-j>=0&&i+j+1<n;j++)//j is the length of substr
{
//even length:
if(s[i-j]!=s[i+j+1])
break;
curmax=j*2+2;
}
if(curmax>max)
max=curmax;
}
return max;
}
解法2:manacher算法
参考:https://www.felix021.com/blog/read.php?2040
我们上面的解法分了奇数长度和偶数长度两种情况讨论,并且复杂度也不低,我们尝试着改进一下:
首先,我们解决奇数和偶数:我们在字符串里面插入#号后看看发生了什么:
abbc->#a#b#b#a# abdba->#a#b#d#b#a#
这样所有的都变成了奇数长度,就可以统一处理了。我们还可以在字符串首加入另一个符号比如$来将字符串的下标0转换为更熟悉的下标1。
以字符串12212321为例,经过上一步,变成了 S[] = "$#1#2#2#1#2#3#2#1#";
然后用一个数组 P[i] 来记录以字符S[i]为中心的最长回文子串向左/右扩张的长度(包括S[i],也就是把该回文串“对折”以后的长度),比如S和P的对应关系:
S # 1 # 2 # 2 # 1 # 2 # 3 # 2 # 1 #
P 1 2 1 2 5 2 1 4 1 2 1 6 1 2 1 2 1
(p.s. 可以看出,P[i]-1正好是原字符串中回文串的总长度)所以我们的问题转化为,如何计算
p[i]
的值我们来发现一条规律,来从一定程度上简化计算:
假如我们位置为id的地方,其长度为p[id],记右边界为mx=id+p[id]
;
(实际上是mx-1吧)
那么其左边界就是id-p[id]
假设有一个位置i,其正好在id这个字符为中心的回文字符串里面
(i<mx)
,那么根据回文的对称性,其左边会有一个与之对称的位置j,j=2*id-i
;
那么这个能干什么呢?因为我们是从左往右边求的,所以直接可以得到右边位置i的最小回文长度!
因为回文的对称性,左边的长度如果是x的话,且j-x>mx的对称点
,即位置j的回文子串全部在以id为中心的字符串里面的话,可以推出以i为中心的回文字符串的长度最小也是j的回文长度p[j];但是如果j的回文字符串超出了mx对称点的边界,也可以说,p[i]的值最少为
mx-i
得到上述两种情况的最小长度后,我们再对位置i的字符进行判断,是否有更长的字符满足回文。
这些思路转化为代码就是:
for(i = 1; s[i] != '\0'; i++)
{
p[i] = mx > i ? min(p[2*id-i], mx-i) : 1;
while(s[i + p[i]] == s[i - p[i]])
p[i]++;
if(i + p[i] > mx)
{
mx = i + p[i];
id = i; }
}
把之前的内容整理下:
- 预处理字符串的函数:
char c[1000];
int func(char* s)
{
if (!s)
return NULL;
int n = strlen(s);
c[0] = '$';
int i = 0, j = 1;
while (i < n)
{
c[j++] = '#';
c[j++] = s[i++];
}
c[j] = '#';
return j;
}
主干:
int solution_manacher(char* s)
{
if (!s)
return 0;
int len = func(s);//length of c
int i = 1;
int curmax = 0;
int p[1000];//p[i] is the length of ith char in c
p[0] = 0;
int id=1 , mx = 0;//mx=id+p[id],but id=????????
for (i = 1; i<len; i++)
{
p[i] = 1;
//if (mx>i)
// p[i] = minTwo(mx - i, p[2 * id - i]);//actually pi>=minTwo
//else p[i] = 1;//one char,itself
if (mx > i)
{
p[i] = p[2 * id - i];
if (mx - i < p[i])
p[i] = mx - i;
}
while (c[i + p[i]] == c[i - p[i]])
p[i]++;
if (i + p[i]>mx)
{
mx = i + p[i];
id = i;
}
if (p[i]>curmax)
curmax = p[i];
}
return curmax-1;
}
最长公共子串
由于这个情况限制只能是连续的,所以情况要简单一些,方程也退化为:
思路和上次差不多,也是创建了一个二维数组:
#include <iostream>
int lcsubstr(char* s1, char* s2)
{
if (!s1 || !s2)
return 0;
int len1 = strlen(s1);
int len2 = strlen(s2);
int i = 0, j = 0;
int max=0;
int a[100][100];
memset(a, 0, sizeof(a));
for (i = 0; i < len1; i++)
{
for (j = 0; j < len2; j++)
{
if (s1[i] == s2[j])
a[i + 1][j + 1] = 1+a[i][j];//careful! i+1&j+1
if (a[i + 1][j + 1]>max)
max = a[i + 1][j + 1];
}
}
return max;
}
int main()
{
char s1[] = "abcd";
char s2[] = "bcdef";
std::cout << lcsubstr(s1, s2);
}
不知道有没有bug额。。