KMP字符串匹配【模板】

链接:登录—专业IT笔试面试备考平台_牛客网
来源:牛客网
 

题目描述

给出两个只含大写英文字母的字符串 s1s_1s1​ 和 s2s_2s2​,若 s1s1s1 的区间[l,r][l,r][l,r]子串与 s2s_2s2​ 完全相同,则称s2s_2s2​在 s1s_1s1​ 中出现了,其出现位置为lll。
现在请你求出 s2s_2s2​在 s1s_1s1​ 中所有出现的位置。

定义一个字符串 sss 的 border 为 sss 的一个sss本身的子串ttt,满足 ttt 既是 sss 的前缀,又是 sss 的后缀。
对于 s2s_2s2​,你还需要求出对于其每个前缀 s′s's′ 的最长 border t′t't′ 的长度。

输入描述:

第一行为一个字符串,即为 s1s_1s1​

第二行为一个字符串,即为 s2s_2s2​

1≤∣s1∣,∣s2∣≤1061\le|s_1|,|s_2| \le 10^61≤∣s1​∣,∣s2​∣≤106

输出描述:

首先输出若干行,每行一个整数,按从小到大的顺序 输出 s2s_2s2​ 在 s1s_1s1​ 中出现的位置

最后一行输出 ∣s2∣|s_2|∣s2​∣ 个整数,第 iii 个整数表示 s2s_2s2​ 的长度为 iii 的前缀的最长 border 长度。

输入:

ABABABC
ABA

输出:

1
3
0 0 1

答案:

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int N = 1e6 + 5;
int n, m, ne[N];
string s, t;
int main() {
	cin.tie(0)->sync_with_stdio(0);
	cin >> s >> t;
	n = s.length();
	m = t.length();
	for(int i = 2; i <= m; ++ i) {
		int j = i - 1;
		while(j) {
			if(t[ne[j]] == t[i - 1]) {
				ne[i] = ne[j] + 1;
				break;
			}
			j = ne[j];
		}
	}
	for(int i = 1, j = 0; i <= n; ++ i) {
		while(j && s[i - 1] != t[j]) {
			j = ne[j];
		}
		j += (s[i - 1] == t[j]);
		if(j == m) {
			cout << i - m + 1 << '\n';
			j = ne[j];
		}
	}
	for(int i = 1; i <= m; ++ i) {
		cout << ne[i] << ' ';
	}
	return 0;
}

解释:

下面的部分即为计算第二个字符串的prefix_table(叫法不一样但原理都是该字符串对应的用于比较前后缀所得到的数组)在此处命名为ne数组

for(int i = 2; i <= m; ++ i) {
		int j = i - 1;
		while(j) {
			if(t[ne[j]] == t[i - 1]) {
				ne[i] = ne[j] + 1;
				break;
			}
			j = ne[j];
		}
	}

下列代码为两个字符串比较的过程运用KMP的方法比较 

for(int i = 1, j = 0; i <= n; ++ i) {
		while(j && s[i - 1] != t[j]) {
			j = ne[j];
		}
		j += (s[i - 1] == t[j]);//(s[i - 1] == t[j])相等时输出1;
		if(j == m) {
			cout << i - m + 1 << '\n';
			j = ne[j];
		}
	}

ai解释:

这段代码实现的是KMP(Knuth-Morris-Pratt)算法,用于在一个文本字符串s中查找一个模式字符串t的所有出现位置,并输出这些位置的起始索引(从1开始计数),以及在最后输出模式字符串t的部分匹配表(也称为失败函数或前缀函数)。

KMP算法的核心思想:

  1. 预处理部分匹配表(Next数组):首先,对模式字符串t进行预处理,构建一个部分匹配表(或称为前缀函数表),这个表用于在模式字符串自身出现不匹配时,决定模式字符串应该从哪里开始重新与文本字符串进行比较,以避免从头开始比较的浪费。

  2. 搜索文本字符串:使用预处理得到的部分匹配表,在文本字符串s中搜索模式字符串t。如果发生不匹配,则根据部分匹配表决定模式字符串应该跳转到哪个位置继续比较。

代码解析:

  • 部分匹配表(Next数组)的构建
    • 数组ne用于存储部分匹配表,其中ne[i]表示模式字符串t中,以t[i-1]结尾的最长相同前后缀的长度(不包括t[i-1]自身)。
    • 使用j变量表示当前正在比较的前缀的末尾位置,通过迭代计算ne数组的值。
  • 在文本字符串中搜索模式字符串
    • 使用两个指针ij分别遍历文本字符串s和模式字符串t
    • s[i-1]t[j]不匹配时,根据ne[j]的值调整j的位置,继续比较。
    • 如果j等于模式字符串的长度m,则说明找到了一个匹配,输出匹配开始的位置(i-m+1),并调整jne[j]以继续搜索下一个可能的匹配。
  • 输出部分匹配表
    • 在所有匹配搜索完成后,输出模式字符串t的部分匹配表。

注意事项:

  • 代码中使用了cin.tie(0)->sync_with_stdio(0);来加速输入输出,这是C++中常用的优化技巧,但在某些环境下(如某些在线编程平台)可能不起作用或导致不可预测的行为。
  • 数组ne的初始化(即ne[0]的值)在代码中未显式设置,但在KMP算法中,通常将ne[0]设置为-1或0(取决于具体实现),这里由于C++的全局变量自动初始化为0,所以ne[0]默认为0,这在KMP算法中是合理的。
  • 代码中直接使用了string类型来存储文本和模式字符串,简化了字符串处理的操作。
  • 输出匹配位置时,是从1开始计数的,这符合很多编程竞赛和实际应用场景的需求。

 

 

 

 

 

 

 

 

  • 30
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值