字符串算法
字符串算法
学习一些常见的字符串算法。
KMP(普通版)
子串badcb的next数组为:
| 最长相等前后缀 | |
|---|---|
| ne[1] | 0 |
| ne[2] | 0 |
| ne[3] | 0 |
| ne[4] | 0 |
| ne[5] | 1 |
kmp重分利用了之前已经比较过的信息来减少时间复杂度。
因为bad没有相等前后缀所有j从0开始
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 1000010;
char s[N], p[N];
int ne[N];
int main(){
scanf("%s%s", s + 1, p + 1);
int m = strlen(s + 1), n = strlen(p + 1);
for(int i = 2, j = 0; i <= n; i ++ ){
while(j && p[i] != p[j + 1]) j = ne[j];
if(p[i] == p[j + 1]) j ++ ;
ne[i] = j;
}
for(int i = 1, j = 0; i <= m; i ++ ){
while(j && s[i] != p[j + 1]) j = ne[j];
if(s[i] == p[j + 1]) j ++ ;
if(j == n){
printf("%d\n", i - n + 1);
j = ne[j];
}
}
for(int i = 1; i <= n; i ++ ) printf("%d ", ne[i]);
return 0;
}
扩展 KMP
z函数定义如下:
z[i]表示字符串s和s[i,n]的最长相等前缀
abababba
| 子串 | 最长相等前缀 | |
|---|---|---|
| z[1] | abababba | 8 |
| z[2] | bababba | 0 |
| z[3] | ababba | 4 |
| z[4] | babba | 0 |
| z[5] | abba | 2 |
| z[6] | bba | 0 |
| z[7] | ba | 0 |
| z[8] | a | 1 |
求解z函数

初始状态
i = 2, z[1] = 9, r = 0;

ab比较不相等z[2]=0

i = 3;
i + z[i] - 1 > r
z盒子边界l=i=3,r=i +z[i] - 1=3 + 4 -1=6;
下一个字符b在盒子内部,直接z[4]=z[4 - 3 + 1] = z[2]=0即z[i] = z[i - l + 1];
下一个字符s[5]=a,r - i + 1 = 6 - 5 + 1 = 2,z[i - l + 1] = z[5 - 3 + 1] = z[3] = 4,因为不能超过z盒子的剩余长度2,所以z[5] = 2;
z盒子内部直接拷贝即可z[i] = z[i - l + 1],如果被拷贝的z[i - l + 1]进入z盒子则不能超过z盒子的剩余长度r-i+1;
z盒子外部暴力枚举。
i = 7时
i + z[i] - 1 = 7 + z[7] - 1 = 6,没有有效z盒子。
实战
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef unsigned long long ULL;
static const int N = 2e7 + 10;
char a[N], b[N];
int z[N], p[N];
void getZ(const char *a, const int m){
z[1] = m;
for(int i = 2, l, r = 0; i <= m; i ++ ){
if(i <= r) z[i] = min(z[i - l + 1], r - i + 1);
while(a[1 + z[i]] == a[i + z[i]]) z[i] ++ ;
if(i + z[i] - 1 > r){
l = i;
r = i + z[i] - 1;
}
}
}
void getP(const char *a, const int m, const char *b, const int n){
for(int i = 1, l, r = 0; i <= n; i ++ ){
if(i <= r) p[i] = min(z[i - l + 1], r - i + 1);
while(1 + p[i] <= m && i + p[i] <= n && a[1 + p[i]] == b[i + p[i]]) p[i] ++ ;
if(i + p[i] - 1 > r){
l = i;
r = i + p[i] - 1;
}
}
}
int main(){
scanf("%s%s", a + 1, b + 1);
const int m = strlen(a + 1), n = strlen(b + 1);
getZ(b, n);
getP(b, n, a, m);
ULL res = 0;
for(int i = 1; i <= n; i ++ ){
res ^= 1ULL * i * (z[i] + 1);
}
printf("%llu\n", res);
res = 0ULL;
for(int i = 1; i <= m; i ++ ){
res ^= 1ULL * i * (p[i] + 1);
}
printf("%llu\n", res);
return 0;
}
Manacher
首先将字符串长度转为奇数
当字符串长度为n隔板法插入特殊字符n+1,总长为2n+1为奇数
d函数代表以i为对称轴的最长回文子串的长度的一半(包括i)

d盒子内部利益i对称位置加速计算。
P3805
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 3e7;
char s[N], a[N];
int d[N];
int main(){
int ans = -1;
scanf("%s", a + 1);
int n = strlen(a + 1), k = 0;
s[0] = '$', s[ ++ k] = '#';
for(int i = 1; i <= n; i ++ ){
s[ ++ k] = a[i], s[ ++ k] = '#';
}
n = k;
d[1] = 1;
for(int i = 2, l, r = 1; i <= n; i ++ ){
if(i <= r) d[i] = min(d[r - i + l], r - i + 1);
while(s[i - d[i]] == s[i + d[i]]) d[i] ++ ;
if(i + d[i] - 1 > r) l = i - d[i] + 1, r = i + d[i] - 1;
ans = max(ans, d[i]);
}
printf("%d", ans - 1);
return 0;
}
5. 最长回文子串
最优算法Manacher
class Solution {
public:
string longestPalindrome(const string s) {
string res;
res.reserve(2 * s.size() + 1);
for(auto c : s){
res.push_back('#');
res.push_back(c);
}
res.push_back('#');
vector<int> d(res.size());
d[0] = 1;
int bestCenter = 0;
for(int i = 1, l, r = 0; i < res.size(); i ++ ){
if(i <= r) d[i] = min(d[r - i + l], r - i + 1);
while(i - d[i] >=0 && i + d[i] < res.size() && res[i - d[i]] == res[i + d[i]]) d[i] ++ ;
if(i + d[i] - 1 > r) {
l = i - d[i] + 1, r = i + d[i] - 1;
}
if(d[i] > d[bestCenter]){
bestCenter = i;
}
}
// 从res映射回s
int start = (bestCenter - d[bestCenter] + 1) / 2;
int len = d[bestCenter] - 1;
return s.substr(start, len);
}
};


浙公网安备 33010602011771号