KMP入门与算法题实践

基础知识

参考视频

下面是两个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算法主要包括两个部分:

  1. 构建部分匹配表(又称前缀函数):该表用于记录每个位置之前的最长相等前后缀的长度。
  2. 字符串匹配过程:利用部分匹配表来加速匹配过程。

算法流程:

  1. 初始化: i 后缀末尾从 1 开始,pre 前缀末尾从 0 开始
  2. 更新 next:i 一直往前,pre 一直回退
    a. 前后缀不相等:连续回退,pre 跳到 next[pre-1] 所指的位置(pre 指针所停留的地方一定是最新的公共前后缀结尾)
    b. 前后缀相等:pre++,next[pre] = pre
  3. 找子串: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;
}
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值