惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cisco Talos Blog
Cisco Talos Blog
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
J
Java Code Geeks
博客园_首页
Scott Helme
Scott Helme
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
Security Latest
Security Latest
V
Visual Studio Blog
Cloudbric
Cloudbric
Jina AI
Jina AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
A
Arctic Wolf
C
Cybersecurity and Infrastructure Security Agency CISA
S
SegmentFault 最新的问题
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
W
WeLiveSecurity
K
Kaspersky official blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hacker News: Ask HN
Hacker News: Ask HN
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
宝玉的分享
宝玉的分享
Hugging Face - Blog
Hugging Face - Blog
量子位
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 三生石上(FineUI控件)
Recent Commits to openclaw:main
Recent Commits to openclaw:main

Blog Feed

浅谈分布式事务#2 - 字节星球 浅谈分布式事务 - 字节星球 聊聊大学里面的奇葩 - 字节星球 MySQL 加锁机制分析与死锁排查 - 字节星球 Golang pprof 案例实战 - 字节星球 Golang 手写一个 Channel - 字节星球 字节星球终于全栈上新! - 字节星球 欢迎试用 TodoList - 字节星球 记一次“说走就走”的成都行 - 字节星球 Docker Desktop修改默认存储路径 - 字节星球 流程中心使用指南 - 字节星球 微分方程小手册 - 字节星球 近期小记 - 字节星球 字节星球(肥柴之家)搬家了! - 字节星球 如何顺利注册ChatGPT? - 字节星球 披着CLion的外衣实则在讲CMake - 字节星球 守望之墓/电子骨灰盒 - 字节星球 Python笔记 第三章 - 字节星球 WePlanet现已发布! - 字节星球 简单选择排序和堆排序 - 字节星球 希尔排序 - 字节星球 插入排序 - 字节星球 快速排序 - 字节星球 Python 笔记 第二章 - 字节星球 Python笔记 第一章 - 字节星球 Web使用HarmonyOS字体的压缩方案 - 字节星球 字节星球关于在评论区等位置展示IP属地的公告 - 字节星球 MATLAB简明教程#1 - 字节星球 解决Qt5无法连接MySQL数据库的问题 - 字节星球 时隔多年,终于摆脱了控制台 - 字节星球 论内卷 - 字节星球 堆排序 - 字节星球 连通块中点的数量 - 字节星球 合并集合(并查集) - 字节星球 Trie字符串统计 - 字节星球
KMP字符串 - 字节星球
2021-06-09 · via Blog Feed

2021/6/9 15:23:00admin2684 阅读0 点赞0 评论

给定一个模式串 SS,以及一个模板串 PP,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 PP 在模式串 SS 中多次作为字串出现。
求出模板串 PP 在模式串 SS 中所有出现的位置的起始下标。

输入格式

第一行输出整数 NN,表示字符串 PP 的长度。
第二行输入字符串 PP
第三行输入整数 MM,表示字符串 SS 的长度。
第四行输入字符串 MM

输入格式

共一行,输出所有出现位置的起始下标(下标从 0\rm{0} 开始计数),整数之间用空格隔开。

数据范围

1≤N≤104\rm{1} \le N \le 10^4
1≤M≤105\rm{1} \le M \le 10^5

输入样例

输出列表

题解

(KMP) O(m+n)O(m+n)
KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

KMP 算法的关键就是求 next 数组:
next 数组长度与字符串长度一致,每个位置存储对应字符的最长匹配长度。
next 数组即 next[i] = length; 长度为 ii 的数组的前缀和后缀相等的最大长度。 例如 abcdeabcabcdeabc 就是 next[8] = 3; 相等的前缀和后缀最长是 abcabc 长度为 3\rm{3}
为了更透彻理解 next 数组,我们来举一个例子
模板串 PP 为:abababababababab(令其下标从 1\rm{1} 开始)
next[1] 即模板串中取长度为 1\rm{1} 的串,如果匹配失败,则退回到起点,故 next[1]=0
next[2] 即模板串中取长度为 2\rm{2} 的串,显然其前缀 aa 和后缀 bb 不相等,故 next[2]=0
next[3] 即模板串中取长度为 3\rm{3} 的串,其前缀 aa 和后缀 aa 相等,aa 的长度为 1\rm{1},故 next[3]=1

⋮ \vdots

next[6] 即模板串中取长度为 6\rm{6} 的串(abababababab),其前缀 abababab 和后缀 abababab 相等,该字串长度为 4\rm{4} ,故 next[6]=4,以此类推,next[7]=5

到此,我们应该明白 next 数组的意义了,现在我们加上模式串 SS 来看,假设 SS 为:abababcababababcab(同样令其下标从 1\rm{1} 开始),以下矩阵代表当前两个字符串的对应关系:

[abababcabababababx] \left[ \begin{array}{l} {\rm{a b a b a b c a b}}\\ {\rm{a b a b a b a b x}} \end{array} \right]

xx 代表空格),可见在 s[7] 时 cc 与 模板串 p[j + 1] 中 aa 不匹配,此时,我们如果令 j = next[j],当前 j=6j=6j=next[j]=4 后会发生什么,请看矩阵:

[abababcabxxxxxababababxx] \left[ \begin{array}{l} {\rm{a b a b a b c a b x x x}}\\ {\rm{x x a b a b a b a b x x}} \end{array} \right]

为什么要这样操作?
因为我们 next[i] 长度为 ii 的数组的前缀和后缀相等的最大长度,刚才我们已经判断得到 p[1] $ \sim$ p[6] 一定是和 s[1] ∼\sim s[6] 完全匹配的,又因为 next[i]=4,故 p[1] $ \sim$ p[6]的前 4\rm{4} 个字符与后 4\rm{4} 个字符是完全相同的,也就得到 p[1] $ \sim$ p[4] 与 p[3] $ \sim$ p[6]完全相同,则 p[1] $ \sim$ p[4] 也与 s[3] $ \sim$ s[6] 完全相同(因为 p[1] $ \sim$ p[6] 和 s[1] ∼\sim s[6] 完全匹配),所以我们就没必要再从 p[1] 开始枚举,直接从 p[4 + 1]开始即可。

这样操作我们就省去了主串指针的回溯所花费的不必要的时间,接下来的步骤以此类推,详见 C++ 代码部分的 kmp 匹配过程。

C++ 代码

CPP

#include <iostream>

using namespace std;

const int N = 10010, M = 100010;

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

int main()
{
    cin >> n >> (p + 1) >> m >> (s + 1); //下标从1开始
    //求next数组过程
    for (int i = 2, j = 0; i <= n; i++)//i从2开始即可,i=1不匹配的话即是退为0,不考虑
    {
        while(j && p[i] != p[j + 1])
            j = ne[j];
        if(p[i] == p[j + 1])
            j++;
        ne[i] = j;
    }
    //kmp匹配过程
    for (int i = 1, j = 0; i <= m; i++)
    {
        while (j && s[i] != p[j + 1]) //j未回退到起点,且当前不匹配
            j = ne[j];                //ne[j]表示p[j+1]前端和s匹配的最小移动坐标
        if (s[i] == p[j + 1])
            j++;
        if (j == n)
        {
            printf("%d ", i - n);//起始下标
            j = ne[j];
        }
    }
    return 0;
}