版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luke2834/article/details/82953779
题意
- 找最长公共子串
思路
- 用dp的方法很容易在O(n^2)解决问题,这里主要讨论用后缀数组的思路解决这个问题
- 后缀数组里有两个经典的概念或者称为数据结构,就是后缀数组SA,以及高度数组LCP
- SA数组的定义是:将原串S所有的后缀按字典序排序后,定义rank(i)为后缀S[i…]的排名,SA数组是rank数组的逆映射,即SA(rank(i)) = i
- LCP数组的定义是:LCP(i)是后缀S[SA(i)…]和后缀S[SA(i+1)…]的最长公共前缀长度
- 这里就不讨论这两个数组的求解算法了,我们使用比较简单的倍增法求解SA数组,复杂度是O(n log^2 n)的,有了SA数组,求解LCP数组是O(n)的
- 有了LCP数组后,我们先来思考另一个问题,一个数组里两个不同的子串的最长公共子串是多长呢?答案是max(LCP),也就是LCP数组里的最大值。原因的话反证一下很简单,这里简单说明一下,主要是考虑数组其它子串和以i开头的子串的最长公共子串是多长,容易证明能达到最长的只能是以SA(rank(i)-1)或SA(rank(i)+1)开头的子串,那么这个结果都保存在LCP里了,所以遍历一遍LCP就能找到最大值
- 利用上述结论,我们很容易解决新的问题了。可以把两个数组拼在一起,并在拼接处加一个特殊的int,是在两个数组里都没有出现的
- 求出LCP数组后,我们只要找i和SA(i+1)不在同一个字符串的LCP的最大值即可
实现
class Solution {
public:
//size of rank and sa are n+1
//size of lcp is n, definition of lcp[i] is max common prefix of s[sa[i]...] and s[sa[i+1]...]
//input s of getSa and getLcp can be string as well
vector<int> rank, sa, lcp;
void getSa(const vector<int>& s, vector<int>& rank, vector<int>& sa){
int n = s.size();
vector<int> tmp(n+1);
for (int i = 0; i < n; i++){
sa.push_back(i);
rank.push_back(s[i]);
}
sa.push_back(n);
rank.push_back(-1);
for (int k = 1; k <= n; k <<= 1){
auto cmp = [&](int x, int y){
if (rank[x] != rank[y])
return rank[x] < rank[y];
auto tx = x + k > n ? -1 : rank[x + k];
auto ty = y + k > n ? -1 : rank[y + k];
return tx < ty;
};
sort(sa.begin(), sa.end(), cmp);
tmp[sa[0]] = 0;
for (int i = 1; i <= n; i++){
tmp[sa[i]] = tmp[sa[i-1]];
if (cmp(sa[i-1], sa[i])){
tmp[sa[i]]++;
}
}
for (int i = 0; i <= n; i++)
rank[i] = tmp[i];
}
}
void getLcp(const vector<int>& s, const vector<int>& rank, const vector<int>& sa,
vector<int>& lcp){
int n = s.size();
lcp.insert(lcp.begin(), n, 0);
for (int i = 0, h = 0; i < n; i++){
if (h > 0)
h--;
int k = rank[i];
int j = sa[k-1];
while (max(j, i) + h < n && s[j+h] == s[i+h]){
h++;
}
lcp[k-1] = h;
}
}
int findLength(vector<int>& A, vector<int>& B) {
int n = A.size(), m = B.size();
A.push_back(101);
A.insert(A.end(), B.begin(), B.end());
getSa(A, rank, sa);
getLcp(A, rank, sa, lcp);
int ans = 0;
for (int i = 0; i <= n + m; i++){
if (sa[i] < n && sa[i+1] > n || sa[i] > n && sa[i+1] < n){
ans = max(ans, lcp[i]);
}
}
return ans;
}
};