算法学习笔记 2023/04/13

今天学习的是KMP算法

kmp主要应用在字符串匹配上。基本思想是在匹配字符串文本时,可以知道前面的一部分已经匹配过的文本,避免从头开始匹配。

kmp的一种实现方式,next数组,是一种前缀表。用我的方式去理解,在文本串中搜索一个模式串,查找到一个不符合模式串的字符时,回退到模式串和当前匹配的长度。

如何去实现这个方法,需要前缀表来记录模式串应该回退的位置。而前缀表,则是记录 i 个下标以内有多大长度的相同前缀和后缀。

前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。

后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。

使用next数组实现前缀表,构造next数组,实际上就是计算前缀表的过程。next数组实际上是记录模式串每个字符位置的最大前缀后缀长度。

void getNext(int* next, const string& s)// 定义指向next数组的指针,引用模式串s

用i,j代表模式串的后缀和前缀末尾,j不仅是前缀末尾,还是最大相同子串大小。

初始化过程:前缀表的移位操作,可以在初始化时把 j 左移= -1,i 作为后缀,所以应该从 s[1]开始。

int j= -1;
next[0] =j;
for(int i=1; i <s.size(); i++)

遍历过程中,j 必须大于0,所以对比字符时应该是s[i]==s[j+1]。

计算前缀表的逻辑:推理了一下,j像是一个指针,跟随 i 延伸,当出现一个不存在于j 的延伸区间的字符,就要回退到直到出现这个字符的位置或是-1,回退需要多次进行,会是一个循环过程。

while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
    j = next[j]; // 向前回退
}

if (s[i] == s[j + 1]) { // 找到相同的前后缀
    j++;
}
next[i] = j;

next数组的应用比实现简单,遍历文本串的同时遍历模式串,如果出现字符不等,回退到next数组的相应位置。如果模式串遍历完成,即代表字符串包含模式串。

class Solution {
public:
    void getNext(int* next,const string& s){
        int j = -1;
        next[0] = j;
        for(int i = 1; i < s.size(); i++) { // 注意i从1开始
            while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了
                j = next[j]; // 向前回退
            }
            if (s[i] == s[j + 1]) { // 找到相同的前后缀
                j++;
            }
            next[i] = j; // 将j(前缀的长度)赋给next[i]
        }
    }

    int strStr(string haystack, string needle) {
        if(needle.size()==0) return 0;
        int N[needle.size()];
        int t= -1;
        getNext(N,needle);
        for(int i =0; i < haystack.size(); i++){
            while(t>=0 &&haystack[i]!=needle[t+1]){
                t=N[t];
            }
            if(haystack[i]==needle[t+1]){
                t++;
            }
            if(t==(needle.size()-1)) {
                return (i-needle.size()+1);
            }
        }
        return -1;
    }
};

题例: 28 找出字符串第一个匹配的下标

2. 重复的子字符串

第一反应把两个字符串s+s拼起来,去头去尾查找s。

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        string ss=s+s;
        ss.erase(ss.begin());
        ss.erase(ss.end()-1);
        if(ss.find(s)!=std::string::npos) return true;
        else return false;
    }
};

练习刚刚学习的KMP法,如果字符串全部由相同字符组成,那么前缀和后缀最大相同长度是根据 i 线性递增的。如果存在某个重复单元x,那么s.size()=n*x,而最大重复单元等于 (n-1)*x,那么s.size()%(len-next(s.size()+1))一定等于0。+1是因为next数组经过左移,实际的数值应该+1右移。

class Solution {
public:
    void getNext(int* next,string& s){
        int j= -1;
        next[0]=j;
        for(int i=1; i< s.size(); i++){
            while(j>=0&&s[i]!=s[j+1]){
                j=next[j];
            }
            if(s[i]==s[j+1]){
                j++;
            }
            next[i]=j;
        }
    }
    bool repeatedSubstringPattern(string s) {
        if(s.size()==0)return false;
        int next[s.size()];
        getNext(next,s);
        int len = s.size();
        if(next[len-1]!=-1 && len%(len-next[len-1]-1)==0) return true;
        return false;
    }
};

题例: 459 重复的子字符串

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值