题意概述

  • 给定一个模式串S和一个文本串T
  • 要求得到模式串S在文本串T中出现的次数

方法一:Brute_Force算法(朴素模式匹配算法超时)

思路与具体做法

  • 同时遍历文本串和模式串
    • 如果两串对应位相等,接着比较下一位
    • 如果不相等,令模式串回到上一次开始匹配位置的下一位,而文本串从头开始比较
    • 遍历完一遍模式串,令出现次数加一
class Solution {
public:
    int kmp(string S, string T) {
        int i=0,j=0,ans=0;
        while(i<T.size()){
            if(T[i]==S[j]){//两串对应位相等,接着比较下一位 
                i++;j++;
            }else{//不相等,令模式串回到上一次开始匹配位置的下一位,而文本串从头开始比较 
                i=i-j+1;
                j=0;
            }
            if(j==S.size())ans++;//遍历完一遍模式串,令出现次数加一
        }
        return ans;//返回出现次数 
    }
};

时间复杂度和空间复杂度分析

  • 时间复杂度:O((nm)m)O((n-m)*m),最坏情况下每次遍历到串的最后一个字符,下标j都要置0,共需遍历(nm+1)m(n-m+1)*m
  • 空间复杂度:O(1)O(1)仅定义了几个整形变量

方法二:KMP算法

相关知识

  • next数组手算方法
    • 串的前缀:包含第一个字符,且不包含最后一个字符的子串
    • 串的后缀:包含最后一个字符,且不包含第一个字符的子串
    • next[j]:模式串第j个字符匹配失败,由前1~j-1个字符组成的串的最长相等前后缀长度+1
      • 特别的,next[1]=0,(可推出next[2]=1)
  • kmp算法概述
    • 判断模式串pattern是否是文本串text的子串
    • 当文本串和模式串不匹配时,文本串指针i不回溯,模式串指针j=next[j]
    • 算法平均时间复杂度O(n+m),kmp耗时即遍历文本串O(m),求临时数组next耗时O(n)
  • kmp算法优化
    • 优化next数组:当模式串前缀指针j后缀指针i所指向不匹配时next[i]=j,否则再次递归next[i]=next[j]

思路与具体做法

  • step1:求模式串的next数组(程序中next数组使用改进后的,即nextval数组)
    • 初始i指向模式串第一个字符,j指向i的前一个字符。以j为子串首,i为子串尾,对[j,i]内的子串求最长相等前后缀长度
    • 接着遍历模式串
      • 如果子串首尾相等即S[i]==S[j]则ij++比较各对应的下一位 或 对应位不相等但前缀指针j已经回退到起点即j=0,则后缀指针i++正常遍历前缀指针j++才能从起点指向第一个元素 ,然后继续比较前后缀长度
        • S[i]==S[j]后缀指针i所对应的最大相等前缀j的前缀next[j]
        • S[i]!=S[j]后缀指针i所对应的最大相等前缀j 或 j=0后缀指针i的最长相等前后缀长度为0
      • 否则对应位不相等时且前缀指针i仍可回退,让i回退,即指向它的最大相等前缀下标 alt
  • step2:KMP算法
    • 同时遍历文本串和模式串
      • 当对应位相等的时候 或 模式串指针回溯到起点时都没不匹配,都直接匹配下一位
      • 否则模式串指针向前回溯
      • 同时遍历过程中,每遍历完一遍模式串,令出现次数加一 (KMP模板则是直接返回查找到的下标)
class Solution {
public:
    int Next[500500];
	void Get_Next(string S,int Next[]){//求模式串的next数组 
		Next[1]=0;
		int i=1,j=0;//初始i指向模式串第一个字符,j指向i的前一个字符。以j为子串首,i为子串尾,对[j,i]内的子串求最长相等前后缀长度 
		while(i<S.size()){//遍历模式串 
			if(j==0||S[i]==S[j]){//S[i]==S[j]即子串首尾相等 或 j=0对应位不相等但前缀指针j已经回退到起点,接着ij++,继续比较前后缀长度 
				++i;//S[i]==S[j]前后缀都相等时则比较各对应的下一位 或 j=0后缀指针i++正常遍历前缀指针j++才能从起点指向第一个元素 
				++j;
				if(S[i]==S[j])Next[i]=Next[j];//S[i]==S[j]后缀指针i所对应的最大相等前缀j的前缀next[j]
				else Next[i]=j;//S[i]!=S[j]后缀指针i所对应的最大相等前缀j 或  j=0后缀指针i的最长相等前后缀长度为0 
			}else j=Next[j];//对应位不相等时且前缀指针i仍可回退,让i回退,即指向它的最大相等前缀下标 
		}
	}
    int kmp(string S, string T) {
        S=""+S;//方便从下标为1开始求next数组
        T=""+T;
        Get_Next(S,Next);
        int i=1,j=1,ans=0;
        while(i<T.size()){//同时遍历文本串和模式串 
            if(j==0||T[i]==S[j]){//当对应位相等的时候 或 模式串指针回溯到起点时都没不匹配,都直接匹配下一位 
                i++;
                j++; 
            }else j=Next[j];//模式串指针向前回溯 
            if(j==S.size()){//遍历完一遍模式串,令出现次数加一 
                ans++;
                j=Next[j];
            }
        }
        return ans;//返回出现次数 
    }
};

时间复杂度和空间复杂度分析

  • 时间复杂度:O(m+n)O(m+n)预处理求模式串的next数组耗时O(m)O(m) , KMP算法遍历了一次文本串耗时O(n)O(n)
  • 空间复杂度:O(m)O(m)用到了大小为m的辅助数组next