洛谷 P3357【KMP】字符串匹配模板题

题目

洛谷 P3375(【模板】KMP):给出文本串 s₁ 和模式串 s₂,输出 s₂ 在 s₁ 中所有出现位置,并输出 s₂ 每个前缀的最长 border(既是前缀又是后缀的真子串)长度——即 KMP 的 next/LPS 数组。

KMP 算法回顾

KMP(Knuth-Morris-Pratt)在匹配失败时利用已比较的信息,让模式串”跳着走”,避免主串指针回溯,最坏复杂度 O(n + m)。核心是 LPS 数组(最长相同前后缀长度):

  1. 构建 LPSlps[i] 表示模式串前 i+1 个字符的最长相同前后缀长度。指针 i 遍历,j 记录当前匹配长度;s[i]==s[j]lps[i]=j+1,否则回退 j = lps[j-1]
  2. 匹配:主串指针 i 不回退,模式串指针 j 按 LPS 跳转;j 到末尾即找到一个匹配位置,然后 j = lps[j-1] 继续找下一个。

AC 代码(C++17)


#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string s1, s2;
    cin >> s1 >> s2;

    int m = s2.size();
    // 1. 构建 LPS
    vector<int> lps(m, 0);
    for (int i = 1, j = 0; i < m; i++) {
        while (j > 0 && s2[i] != s2[j]) j = lps[j - 1];
        if (s2[i] == s2[j]) j++;
        lps[i] = j;
    }

    // 2. 匹配
    vector<int> pos;
    for (int i = 0, j = 0; i < (int)s1.size(); i++) {
        while (j > 0 && s1[i] != s2[j]) j = lps[j - 1];
        if (s1[i] == s2[j]) j++;
        if (j == m) {
            pos.push_back(i - m + 2);   // 1-based 位置
            j = lps[j - 1];             // 继续找下一个
        }
    }

    for (int p : pos) cout << p << "n";
    for (int i = 0; i < m; i++) cout << lps[i] << (i + 1 == m ? "n" : " ");
    return 0;
}

要点

  • 匹配位置的下标转换:KMP 匹配结束时 i 指向模式串末尾,起点(1-based)为 i - m + 2,写错是常见 WA 点;
  • 找到匹配后继续找:必须 j = lps[j-1] 而不是 j = 0,否则会漏掉重叠出现的模式串;
  • LPS 输出格式:题目要求空格分隔、最后一个无多余空格(或直接换行)。
滚动至顶部