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

推荐订阅源

爱范儿
爱范儿
宝玉的分享
宝玉的分享
博客园 - 司徒正美
Microsoft Azure Blog
Microsoft Azure Blog
博客园 - 三生石上(FineUI控件)
IT之家
IT之家
博客园 - Franky
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Blog — PlanetScale
Blog — PlanetScale
Recent Announcements
Recent Announcements
Stack Overflow Blog
Stack Overflow Blog
T
Threatpost
aimingoo的专栏
aimingoo的专栏
博客园_首页
Know Your Adversary
Know Your Adversary
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Proofpoint News Feed
Security Latest
Security Latest
T
Tailwind CSS Blog
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
Latest news
Latest news
G
GRAHAM CLULEY
C
CERT Recently Published Vulnerability Notes
Engineering at Meta
Engineering at Meta
A
Arctic Wolf
D
Darknet – Hacking Tools, Hacker News & Cyber Security
I
InfoQ
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园 - 叶小钗
雷峰网
雷峰网
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
S
SegmentFault 最新的问题
C
Cybersecurity and Infrastructure Security Agency CISA
Last Week in AI
Last Week in AI
罗磊的独立博客
博客园 - 聂微东
Simon Willison's Weblog
Simon Willison's Weblog
Scott Helme
Scott Helme
T
Tenable Blog
O
OpenAI News
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
L
LINUX DO - 热门话题
美团技术团队

Blog Feed

锐评简笔记/某站商店/某彦 - 字节星球 浅谈 DDD 领域驱动设计架构 - 字节星球 浅谈分布式事务#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;
}