基础知识
参考视频
下面是两个b站上个人借鉴学习的视频
第一个视频用来快速理解KMP:
【最浅显易懂的 KMP 算法讲解】 https://www.bilibili.com/video/BV1AY4y157yL/?share_source=copy_web&vd_source=d124eda224bf54d0e3ab795c0b89dbb0
第二、三个视频用来理解流程和写代码:
【帮你把KMP算法学个通透!(求next数组代码篇)】 https://www.bilibili.com/video/BV1M5411j7Xx/?share_source=copy_web&vd_source=d124eda224bf54d0e3ab795c0b89dbb0
文字流程
KMP(Knuth-Morris-Pratt)算法是一种用于字符串匹配的高效算法。它利用之前部分匹配的结果来避免重复的匹配操作,从而提高匹配效率。
KMP算法主要包括两个部分:
- 构建部分匹配表(又称前缀函数):该表用于记录每个位置之前的最长相等前后缀的长度。
- 字符串匹配过程:利用部分匹配表来加速匹配过程。
算法流程:
- 初始化: i 后缀末尾从 1 开始,pre 前缀末尾从 0 开始
- 更新 next:i 一直往前,pre 一直回退
a. 前后缀不相等:连续回退,pre 跳到 next[pre-1] 所指的位置(pre 指针所停留的地方一定是最新的公共前后缀结尾)
b. 前后缀相等:pre++,next[pre] = pre - 找子串:i 一直往前,j 一直回退
a. 从前往后遍历模式串
b. 相等 j++
c. 不相等连续回退 j 跳到 next[j-1]
d. 子串匹配完毕返回 i-length
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if (needle.charAt(j) == haystack.charAt(i))
j++;
if (j == needle.length())
return i - needle.length() + 1;
}
return -1;
}
private void getNext(int[] next, String s) {
int j = 0;
next[0] = 0;
for (int i = 1; i < s.length(); i++) {
while (j > 0 && s.charAt(j) != s.charAt(i))
j = next[j - 1];
if (s.charAt(j) == s.charAt(i))
j++;
next[i] = j;
}
}
算法题
P3375 模板 KMP
洛谷P3375
模板代码如下:
#include <vector>
#include <string>
#include <iostream>
using namespace std;
vector<int>nextNum;
string s1;
string s2;
void getNext(string& s1){
int pre = 0;
nextNum.push_back(0);
for(int i = 1;i<s1.length();i++){
while(pre>0&&s1[pre]!=s1[i]){
pre = nextNum[pre-1];
}
if (s1[pre]==s1[i]) {
pre++;
}
nextNum.push_back(pre);
}
}
int main(){
cin>>s1>>s2;
getNext(s2);
int j = 0;
vector<int> res;
for(int i = 0;i<s1.length();i++){
while(j>0&&s1[i]!=s2[j]){
j = nextNum[j-1];
}
if (s1[i]==s2[j]) {
j++;
}
if (j == s2.length()) {
int t = i - s2.length() + 2;
res.push_back(t);
j = nextNum[j-1];//假装失败
}
}
for(int v : res){
cout<<v<<endl;
}
for(int v : nextNum){
cout<<v<<" ";
}
return 0;
}
P4391无线传输
思路:答案是 n - next[n-1]
一个字符串的公共前后缀长度范围为 0-n-1,也就是没有相同的和都是同一个字符
重复循环的字符串,根据上面的连等式,最大的公共前后缀一定至少错开一个周期,不然就都是一个字符(实践也是如此,找不出其他情况)
最大的公共前后缀一定至少错开一个周期,意味着 len-最后的公共前后缀长度=最短的周期长度(一定是最后的 next[n-1] ,不是最大)
acbca 5-1=4
acbac 5-2=3
#include <vector>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;
vector<int>nextNum;
int n;
string s1;
void getNext(string& s1){
int pre = 0;
nextNum.push_back(0);
for(int i = 1;i<s1.length();i++){
while(pre>0&&s1[pre]!=s1[i]){
pre = nextNum[pre-1];
}
if (s1[pre]==s1[i]) {
pre++;
}
nextNum.push_back(pre);
}
}
int main(){
cin>>n>>s1;
if(s1.length()<=1){
cout<<s1.length();
return 0;
}
getNext(s1);
int len = s1.length();
cout<<len-nextNum[n-1];
return 0;
}