zoukankan      html  css  js  c++  java
  • KMP【模板】 && 洛谷 P3375

    题目传送门

    解题思路:

    首先说KMP的作用:对于两个字符串A,B(A.size() > B.size()),求B是否是A的一个字串或B在A里的位置或A里有几个B,说白了就是字符串匹配.

    下面创设一个问题背景 : 有两个字符串A,B,求B在A中的位置.

    A : abcdcbd

    B: cdcb

    对于上述问题,我们最先想到也就是最暴力的办法,就是把B整体从1~7每次一格的往右挪,每往挪一格,便把B从头到尾跟A匹配一遍,直到匹配不成功或全部匹配成功,如果匹配不成功,就再整体往右挪一格,这样的时间复杂度最坏可以达到O(nm).

    如果字符串再长一点,长度达到1e6,显然暴力是会TLE的,那我们就要考虑优化,上述做法唯一能优化的地方就是往右挪的方式,因为一格一格的挪实在是太笨了,所以我们就要想怎样才能挪的快而且正确呢?

    很容易想到的是1.直接移到B的后一位 2.移到失配的位置 ,而这两种做法都是可以被推翻的,见图:

    那我们就只能甘于暴力吗?不,所以我们要学习KMP,进入正题:

    对于学习KMP,要明白几个重要概念:

    对于一个字符串A': abcddc

    1.前缀:a,ab,abc,abcd,abcdd.

    2.后缀:c,dc,ddc,cddc,bcddc.(前缀和后缀都不包括其本身)

    然后我们来看一下KMP的伪代码:

    1 while(i < a.length() && j < b.length()) {
    2     if(当前位置匹配) i++,j++;
    3     else j = kmp[j];  //跳到一个特定的位置 
    4 }
    5 if(j == b.length()) 成功
    6 else 失败 

    看完后发现其实KMP的核心,就是求kmp[j](大多数人称为next[j]),下面就来说一下kmp[j]怎么求.

    kmp[i]表示的是长度为i(1~i)的字符串的最长公共前后缀的长度.(以下的j皆为不包括本次字符求得的最长公共前后缀长度,其实也就是枚举前缀的长度)

    例: P: a b c d a b c 

    kmp[]:0 0 0 0 1 2 3

    求当前kmp[i],可以分两种情况:

    1.当P[i] == P[j+1]时(见下图),kmp[i] = j+1,因为j=2说明P[1..2]和P[3..4]是完全相等的,而P[5]==P[3],那么P[1..3]和P[3..5]是完全相等的,那kmp[5] = 3;

    2.当P[i] != P[j+1]时(见下图),j = kmp[j]往回跳,因为j == 3,说明P[1..3]和P[5..7]完全相等,而后缀的最后加上一个P[i],则意味着我们相当于在P[4]插入一个P[i],要在P[1..3]找一个位置j,使P[i] == P[j+1],转化为第一种情况,求kmp[4],即为kmp[i].那为什么j=kmp[j]呢,举个例子:aba插入一个b,只有两个a相等,后面才有可能成为公共前后缀.还有就是j要跳到什么程度呢?答案就是,当找到P[i]==P[j+1]时,或j跳到0了,说明不可能形成公共前后缀了,就停止.

    下面上求kmp[]的代码:

    1 string l;
    2 j = 0;
    3 kmp[1] = 0;
    4 for(int i = 2;i <= n; i++) {
    5     while(j != 0 && l[i] != l[j+1]) j = kmp[j];
    6     if(l[i] == l[j+1]) j++;
    7     kmp[i] = j;
    8 }

    然后,我们再回归问题,如何在A串中匹配B呢?Talk is cheap,show you the code.

     1 string a,b;
     2 j = 0;
     3 kmp[1] = 0;
     4 for(int i = 2;i <= n; i++) {//n为B的长度 
     5     while(j != 0 && b[i] != b[j+1]) j = kmp[j];
     6     if(b[i] == b[j+1]) j++;
     7     kmp[i] = j;
     8 }
     9 j = 0;
    10 for(int i = 1;i <= m; i++) {//m为A的长度 
    11     while(j != 0 && a[i] != b[j+1]) j = kmp[j];//匹配失败就往回跳 
    12     if(a[i] == b[j+1]) j++;//当前匹配成功 
    13     if(j == n) {//整个B匹配成功 
    14         printf("%d",i - j + 1);
    15         return 0;
    16     }
    17 }

    最后说一点,就是对于A和B匹配的时候,并不是i在向后动,而是j在向前动.

    最后的吐槽时间:这算法好难理解(自认为),想出这算法的三个人也太nb了,写这篇博客好累,虽然不一定有人看,但心里还是挺高兴的.

    AC代码:

    //洛谷题的AC代码

     1 #include<iostream>
     2 #include<cstdio>
     3 
     4 using namespace std;
     5 
     6 string l1,l,pp,p2;
     7 int lena,lenb,next[1000001],j;
     8 
     9 int main() {
    10     l1 = l = " ";
    11     cin >> pp >> p2;
    12     l += pp;l1 += p2;
    13     lena = l.length();
    14     lenb = l1.length();
    15     next[1] = 0;
    16     for(int i = 2;i < lenb; i++) {
    17         while(j != 0 && l1[i] != l1[j+1]) j = next[j];
    18         if(l1[j+1] == l1[i]) j++;
    19         next[i] = j;
    20     }
    21     j = 0;
    22     for(int i = 1;i < lena; i++) {
    23         while(j != 0 && l[i] != l1[j+1]) j = next[j];
    24         if(l[i] == l1[j+1]) j++;
    25         if(j == lenb - 1) {
    26             printf("%d
    ",i - lenb + 2);
    27             j = next[j];
    28         }
    29     }
    30     for(int i = 1;i < lenb; i++)
    31         printf("%d ",next[i]);
    32     return 0;
    33 }
  • 相关阅读:
    LeetCode 350. Intersection of Two Arrays II (两个数组的相交之二)
    LeetCode 349. Intersection of Two Arrays (两个数组的相交)
    LeetCode 290. Word Pattern (词语模式)
    LeetCode 266. Palindrome Permutation (回文排列)$
    34.Search for a Range
    spark连接mongodb
    NLPIR中文分词器的使用
    scala和maven整合实践
    Spark中的键值对操作-scala
    301.Remove Invalid Parentheses
  • 原文地址:https://www.cnblogs.com/lipeiyi520/p/12359489.html
Copyright © 2011-2022 走看看