KMP算法

文章详细介绍了KMP算法的目的,next数组的含义和计算方法,以及如何利用next数组进行字符串匹配时的回退操作。通过实例解析了next数组的计算过程,包括最长相同前后缀的概念,并展示了如何解决重复子字符串的问题。
摘要由CSDN通过智能技术生成

1. KMP算法目的
    当文本串 S 匹配到 i 位置,模式串 P 匹配到 j 位置匹配失败时, i 不动,j 回退,但是根据已知信息尽可能少的回退 

2.  next数组中记录的到底是什么
     next数组记录匹配失败时,模式串的 j 回退位置

3. 计算next数组时,为什么要找最长相同前后缀
       文本串s   a b c a b a
       模式串p   a b c a b c

   (1)模式串是怎么回退的

        当前 a 与 c 不相同,我们希望从文本串的 a 向前 (<----) 找一个子串,它与模板串从下标 0 开始往后 (---->) 的一个字串相同,这个字符串的长度为 n,那么模式串的j就回退到 n

就上面这个例子,绿色背景部分就是相同的字串部分,长度为2,j 就回退到下标 2 也就是 c 进一步进行匹配(为啥回退下标等于相同字串的长度,因为模式串下标从 0 开始)

   (2)回退与后缀的关系

        当文本串匹配到 a 的时候才失败说明,之前匹配是正确的,那么对应模板串的紫色部分跟文本串的绿色部分相同,进而跟模式串的绿色的部分是相同的。那么回退问题就可以与文本串进行脱离,转化为模式串最长前后缀的问题

    (3)手工计算 next 数组注意啥

        既然 next 数组时当 j 这个位置匹配失败后退到哪,那么 next 数组肯定跟 j 没有关系。要看以下标 0 的字符开头,以下标 j - 1 的字符为结尾的不包括自身的最长相同串

4. 如何计算next数组

  (1)规定 next[0] = -1;  next[1] = 0;  如果第一个字符就匹配不上,无路可退, 第二个个字符匹配不上回到开头

 (2)在已知 next[ j ] = k 的前提下如何 计算next[ j + 1 ]

        next[ j ] = k  表示当下标 j 不匹配时回退到下标k 也就是有一个长度为 k 的 相同前后缀字串

        p[ 0 ]……p[ k - 1]    == p[ j - k] …… p[ j -1]   

    (因为子串长度为k,那么后缀字串起始位置为j - 1 - k +1 = j - k)

第一种情况 p [ k ] == p[ j ] 就在之前基础上又加了一个相等

    p[ 0 ]……p[ k - 1] p[ k ]    == p[ j - k] …… p[ j -1] p[ j ]   

    此时 next[ j + 1] = next[ j ] + 1 = k + 1

第二种情况  p [ k ] != p[ j ]    那么 j = k  k = next[ j ] 一直回退 直到 p[ j ] == p[ k ] 

next[ j + 1 ] = k + 1 

(3) 为什么要回退,为什么回退就是可以找到想要的值

第一种解释(我个人的解释):现在是找相同前后缀,实质上还是字符串匹配

 假定

0   1  2  3  4  5  6  7

a   b  c  a  b  a  b  c

-1  0  0  0  1  2 

计算next[ 6 ]  next[ 5 ] = 2   a ! =  c   我们转为下面这种字符串匹配的形式,

a   b  c  a  b  a  b  c

            a   b  c  a  b  a  b  c

跟文本串不相等,模板串那就回退吧   j = next[ j ]。

要么找到模块串回退到的那个字符跟文本串相等,又符合

p[ 0 ]……p[ k - 1] p[ k ]    == p[ j - k] …… p[ j -1] p[ j ]      next[ j + 1 ] = k + 1 

要么就模板串回退到 -1 ,  -1+1 = 0   还是  next[ j + 1 ] = k + 1 

第二种解释:看起来就很对,但是思想我概括不了

假定我们要计算next[ 16 ]  此时 next[15] = 7

也就是 p[ 0 ] …… p[6]  == p[ 8 ]……p[ 14 ]     前后两个部分是重合的。

如果 p[15] == p[7]   next[16] = 7 + 1 = 8

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

7

如果 p[7] != p[15]

假定 next[7] = 3

p[ 0 ] …… p[2]  == p[ 4 ]……p[ 6 ]   ==  p[ 8 ] …… p[10]  == p[ 12 ]……p[ 14 ]     这四个部分是重合的   

重点是首尾两块是相等的   此时如果p[15] ==p[3]   那么next[16] = 3 + 1 = 4

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

3

7

如果p[3] != p[15]

继续回退 next[3] = 1  比较p[15] 与p[1] 。一直回退,直到找到相同或者是下标为-1

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

1

3

7

KMP优化

先计算next数组,再遍历计算next_val数组

避免 aaaaaaaaaaaaaaaaaaab   一个个回退的问题

 28. 找出字符串中第一个匹配项的下标

class Solution {
public:
    int strStr(string haystack, string needle) {
        int n = haystack.size();
        int m = needle.size();
        if(n < m) return -1;

        int next[m];
        getNext(next, needle);

        int i=0;
        int j=0;
        while(i < n && j < m){
            if(j == -1 || haystack[i] == needle[j]){
                i++;
                j++;
            }else{
                j = next[j];
                
            }
        }
        if(j >= m) {
              return i-j;
        }
        return -1;
    }
    void  getNext(int *next, string &needle){
        next[0] = -1;
        if(needle.size()==1) return;   
        next[1] = 0;
        int k = 0;
        for(int i = 2; i< needle.size(); i++){
            while(k != -1 && needle[k] != needle[i-1]){
                k = next[k];
            }
            next[i] = k+1;
            k = next[i];
        }
    }

};

459. 重复的子字符串

以abababab为例 ,ab就是最小重复单位, 最长相等前后缀不包含的子串 ababab  少了的那个就是最小

数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。

之前求最长前后缀的时候,没有带上最后一个元素的,这里需要判断一下

class Solution {
public:
    bool repeatedSubstringPattern(string s) {
        int n = s.size();
        if(n <= 1) return false;
        int next[n];
        next[0] = -1;
        next[1] = 0;
        int k = 0;
        for(int i=2; i<n; i++){
            while(k!=-1 && s[i-1] != s[k]) k = next[k];
            next[i] = k + 1;
            k = next[i];
        }
        if(s[next[n-1]]!=s[n-1]) return false;

        if(n % (n - (next[n-1]+1)) == 0) return true;
        return false;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值