831. KMP字符串

106 阅读1分钟

www.acwing.com/problem/con…

1.PNG

本题是经典的KMP算法。我们先来看看字符串匹配的暴力解法,假设字符串S[N]和匹配串p[M]

bool flag = false;

for(int i = 0; i < N; ++i) {
    flag = true;
    for(int j = 0; j < M; ++j) {
        if(p[j] != s[j + i]) {
            flag = false;
            break;
        }
    }
}

如下图所示,暴力解法的整体思路大概是,当P和S某个位置的字符不一致时,P就整体往前进一位,然后在进行匹配。这样整体的时间复杂度是O(N * M)。

2.PNG

但是,可以观察到已经有一部分字符已经匹配成功,我们是不是可以利用这部分成功匹配的信息来使得P可以尽可能往前进,减少匹配次数。实际上,如下图所示。

3.PNG

我们只需要获知字符串前缀与后缀相等的最大长度即可。

如图所示,如果P只有第一位和最后一位相等,即可以直接将P放置到第三行的位置。

4.PNG

所以,我们需要对p进行处理,获取到每个位置最长前缀字符串的末位置的坐标,这样我们就可以借助这个信息来帮助我们移动P了。

首先,我们先来看一下匹配过程。假设我们已经有了next数组了。

#include <iostream>
using namespace std;

const int N = 100010, M = 1000010;
char s[M], p[N];
int n, m;
int ne[N]; // next数组

int main() {
    cin >> n >> p + 1 >> m >> s + 1;
    
    for(int i = 1, j = 0; i <= m; ++i)
    {
        while(j && p[j + 1] != s[i]) j = ne[j];
        if(p[j + 1] == s[i])
        {
            j++;
        }
        
        if(j == n)
        {
            cout << i - n << " ";
            j = ne[j];
        }
    }
}

接下来,next数组求法与上述相似。

#include <iostream>
using namespace std;

const int N = 100010, M = 1000010;
char s[M], p[N];
int n, m;
int ne[N]; // next数组

int main() {
    cin >> n >> p + 1 >> m >> s + 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 && p[j + 1] != s[i]) j = ne[j];
        if(p[j + 1] == s[i])
        {
            j++;
        }
        
        if(j == n)
        {
            cout << i - n << " ";
            j = ne[j];
        }
    }
}