题意整理

  • 给定模式串S和主串T。
  • 求模式串S在主串T中出现的次数。

方法一(朴素模式匹配)

1.解题思路

  • 首先进行特殊情况判断,如果模式串长度大于主串,或者主串为空,返回0。
  • 然后分别遍历主串和模式串,只要当前字符相等,模式串和主串均后移一位,如果不相等,模式串重新回退到索引0的位置,同时主串索引i回退到上次匹配开头的位置。
  • 当模式串索引达到长度m时,说明全部匹配上了。此时将匹配次数加一,同时主串索引i回退到上次匹配开头的下一位,模式串索引j回到0。

2.代码实现

import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 计算模板串S在文本串T中出现了多少次
     * @param S string字符串 模板串
     * @param T string字符串 文本串
     * @return int整型
     */
    public int kmp (String S, String T) {
        int m=S.length(),n=T.length();
        //特殊情况判断
        if(m>n||n==0) return 0;
        //出现次数
        int cnt=0;
        //分别遍历主串和模式串
        for(int i=0,j=0;i<n;i++){
            //只要不相等,模式串回退到0位置
            while(j>0&&T.charAt(i)!=S.charAt(j)){
                i=i-j+1;
                j=0;
            }
            if(T.charAt(i)==S.charAt(j)) j++;
            //如果j等于m,说明所有字符都匹配上了
            if(j==m){
                //次数加一,同时i回退到上次匹配开头的下一位,j回到0
                cnt++;
                i=i-j+2;
                j=0;
            }
        }
        return cnt;
    }
    
}

3.复杂度分析

  • 时间复杂度:最坏情况下,比如主串是"000001",匹配串是"001",每次到匹配串的最后一个字符,其下标j都要重新置0回到开头,所以需要遍历图片说明 次,复杂度是图片说明
  • 空间复杂度:需要额外常数级别的内存空间,所以空间复杂度为图片说明

方法二(kmp模式匹配)

1.解题思路

朴素模式匹配之所以慢,是因为每次字符不相等时,模式串要回退到初始位置。可以利用之前的匹配信息排除掉多余的回退,关键点就是建立一个next数组来记录模式串索引应该回退的位置,接下来分析怎么确定next数组。

比如模式串为"ababab"时,next数组是[0,0,1,2,3,4]

确定next数组思路:主要是根据之前是否有重复前缀字串来确定应该跳到什么位置,首先索引0位置,没有信息可以参考,直接回退到0;索引1位置,a和b不相等,没有重复前缀字串,直接回退到0;索引2位置,这时候有公共前缀a,回退到前缀的下一个索引1;索引3位置,这时候有公共前缀ab,回退到前缀的下一个索引2;索引4位置,这时候有公共前缀aba,回退到前缀的下一个索引3;索引5位置,这时候有公共前缀abab,回退到前缀的下一个索引4(上面所说的公共前缀尽可能取最长的)。

基本步骤:

  • 首先进行特殊情况判断,与朴素方法一致。
  • 然后分别遍历主串和模式串,只要当前字符相等,模式串和主串均后移一位,如果不相等,模式串重新回退到next数组指定的下一位索引。
  • 当模式串索引达到长度m时,说明全部匹配上了。此时将匹配次数加一,同时模式串索引j回退到next数组指定的下一位索引。

遍历的过程中,除了模式串可以直接回退到next数组指定的下一位外,还有一个优化点,即每完成一次匹配,主串不需要回退了。

动图展示: 图片说明

2.代码实现

import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 计算模板串S在文本串T中出现了多少次
     * @param S string字符串 模板串
     * @param T string字符串 文本串
     * @return int整型
     */
    public int kmp (String S, String T) {
        //特殊情况判断
        int m=S.length(),n=T.length();
        if(m>n||n==0) return 0;
        
        //初始化计数,获取next数组
        int cnt=0;
        int[] next=getNext(S);
        
        //遍历主串和模式串
        for(int i=0,j=0;i<n;i++){
            //只要不相等,回退到next数组记录的下一位
            while(j>0&&T.charAt(i)!=S.charAt(j)){
                j=next[j-1];
            }
            if(T.charAt(i)==S.charAt(j)) j++;
            //如果j为m,说明完全匹配一次
            if(j==m){
                //计数加一,索引回退到next数组记录的下一位
                cnt++;
                j=next[j-1];
            }
        }
        return cnt;
    }
    
    //确定next数组
    private int[] getNext(String S){
        int m=S.length();
        int[] next=new int[m];
        for(int i=1,j=0;i<m;i++){
            //只要不相等,回退到next数组记录的下一位
            while(j>0&&S.charAt(i)!=S.charAt(j)){
                j=next[j-1];
            }
            //前缀索引后移
            if(S.charAt(i)==S.charAt(j)) j++;
            //确定应该回退到的下一个索引
            next[i]=j;
        }
        return next;
    }
    
}

3.复杂度分析

  • 时间复杂度:预处理出next数组需要图片说明 的时间复杂度,而匹配的过程需要遍历一遍主串,复杂度为图片说明,所以总的时间复杂度是图片说明
  • 空间复杂度:需要额外大小为m的next数组,所以空间复杂度为图片说明