字符串算法

字符串算法

学习一些常见的字符串算法。

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函数
image
初始状态
i = 2, z[1] = 9, r = 0;
image
ab比较不相等z[2]=0
image
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盒子。

实战

P5410

#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)
image
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);
    }
};

image

posted @ 2026-07-12 10:52  爱情丶眨眼而去  阅读(10)  评论(0)    收藏  举报