暴力匹配或KMP算法解决字符串匹配问题

字符串匹配问题

  • 1. 字符串匹配问题
  • 2. 解决方案
    • 2.1 暴力匹配算法
      • 2.1.1 算法步骤
      • 2.1.2 代码实现
    • 2.2 KMP算法
      • 2.2.1 算法步骤
      • 2.2.2 next数组计算
      • 2.2.2 代码实现
  • 3. 真题
    • 3.1 力扣 28. 找出字符串中第一个匹配项的下标
    • 3.2 力扣 459. 重复的子字符串
    • 3.3 NC149 kmp算法
    • 3.4 KMP算法

1. 字符串匹配问题

  • 给定字符串S,和字符串T,查询T在S中出现的位置即为字符串匹配问题;
    暴力匹配或KMP算法解决字符串匹配问题_第1张图片

2. 解决方案

2.1 暴力匹配算法

2.1.1 算法步骤

  • 使用双指针,指针i指向S串进行遍历,指针j指向T进行遍历;
  • 一旦某时刻S[I]!=T[j],则说明当前子串不匹配,需要重新开始匹配,即i=i-j+1(S中匹配操作新的起始位置),j=0(T从头开始匹配);
  • 当j=T.length时说明匹配成功;
    暴力匹配或KMP算法解决字符串匹配问题_第2张图片

2.1.2 代码实现

package com.northsmile.string;

/**
 * @author NorthSmile
 * @version 1.0
 * @date 2023/8/22&1:20
 * 暴力匹配算法解决字符串匹配问题
 */
public class StrMatch {
    public static void main(String[] args) {
        String s="abdbcabcdef";
        String t="abc";
        System.out.println(match(s,t));
    }

    public static int match(String s,String t){
        if (t.length()>s.length()){
            return -1;
        }
        if (s.equals(t)){
            return 0;
        }
        int i=0,j=0;
        while (i<s.length()&&j<t.length()){
            if (s.charAt(i)==t.charAt(j)){
                i++;
                j++;
            }else{
                i=i-j+1;
                j=0;
            }
        }
        return j==t.length()?i-j:-1;
    }
}

2.2 KMP算法

  • 暴力匹配算法缺点:匹配过程中,一旦匹配失败,文本串要将当前匹配起点+1作为新的起点,在文本串和模式串长度较大时,性能比较低下;
  • 使用KMP算法进行字符串匹配,通过利用字符串的前缀和后缀的最长公共子串,降低不必要的无效匹配操作,可提升匹配速度;
    暴力匹配或KMP算法解决字符串匹配问题_第3张图片

2.2.1 算法步骤

暴力匹配或KMP算法解决字符串匹配问题_第4张图片

2.2.2 next数组计算

  • 计算每个位置对应的前缀与后缀最大公共长度,得到最大公共长度表;
  • 将最大长度均右移一位,用-1填充第一个位置(如果第一个位置要求0开头,则将此事的next数组元素均+1即可);
    暴力匹配或KMP算法解决字符串匹配问题_第5张图片
    暴力匹配或KMP算法解决字符串匹配问题_第6张图片

2.2.2 代码实现

package com.northsmile.string;

import java.util.Arrays;

/**
 * @author NorthSmile
 * @version 1.0
 * @date 2023/8/22&1:20
 * KMP算法
 * 目的:i不回退,j回退到特定的位置
 */
public class KMP{
    public static void main(String[] args) {
        String str="abdbcabcdef";
        String pattern="abc";
//        String pattern="abcababcabc";
//        String str="BBC ABCDAB ABCDABCDABDE";
//        String pattern="ABCDABD";
//        String pattern="AAAB";
        System.out.println(Arrays.toString(calNext(pattern)));
        System.out.println(match(str,pattern,0));
    }

    /**
     * 从str的pos位置查找pattern
     * @param str
     * @param pattern
     * @param pos
     * @return
     */
    public static int match(String str,String pattern,int pos){
        if (str==null||pattern==null){
            return -1;
        }
        if (pattern.length()>str.length()){
            return -1;
        }
        if (pos<0||pos>=pattern.length()){
            return -1;
        }
        if (str.equals(pattern)){
            return 0;
        }
        int[] next=calNext(pattern);
        // i指向文本串,j指向模式串
        int i=pos,j=0;
        while (i<str.length()&&j<pattern.length()){
        	// j=-1表示两个串第一个字符就不匹配
            if ((j==-1)||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                // 回退j
                j = next[j];
            }
        }
        return j==pattern.length()?i-j:-1;
    }

    // 字符串对应next数组的计算
    public static int[] calNext(String str){
        int n=str.length();
        int[] next=new int[n];
        next[0]=-1;
        next[1]=0;
        for (int i=2,k=next[1];i<n;i++){
        	// k=-1表示前缀和后缀没有公共串
            if (k==-1||str.charAt(i-1)==str.charAt(k)){
                next[i]=k+1;
                k=next[i];
            }else{
                k=next[k];
                i--;
            }
        }
        return next;
    }
}



3. 真题

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

class Solution {
    public int strStr(String haystack, String needle) {
        return kmp(haystack,needle);
    }

    public int kmp(String str, String pattern){
        if(str==null||pattern==null){
            return -1;
        }
        if(str.length()==0||pattern.length()==0){
            return -1;
        }
        if(pattern.length()>str.length()){
            return -1;
        }
        if(pattern.equals(str)){
            return 0;
        }
        // 计算模式串的next数组
        int[] next=getNext(pattern);
        // 匹配查找
        int i=0,j=0;
        while(i<str.length()&&j<pattern.length()){
            if(j==-1||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                // j回退
                j=next[j];
            }
        }
        return j==pattern.length()?i-j:-1;
    }

    public int[] getNext(String str){
        int n=str.length();
        if(n==1){
            return new int[]{-1};
        }
        if(n==2){
            return new int[]{-1,0};
        }
        int[] next=new int[n];
        next[0]=-1;
        next[1]=0;
        // k用于记录i-1位置需要回退的位置
        int i=2,k=0;
        while(i<n){
            if(k==-1||str.charAt(i-1)==str.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                // k回退
                k=next[k];
            }
        }
        return next;
    }
}

3.2 力扣 459. 重复的子字符串

class Solution {
    // 字符串匹配
    public boolean repeatedSubstringPattern(String s) {
        return kmp(s+s,s,1)!=s.length();
    }

    public int kmp(String str, String pattern,int pos){
        if(str==null||pattern==null){
            return -1;
        }
        if(str.length()==0||pattern.length()==0){
            return -1;
        }
        if(pattern.length()>str.length()){
            return -1;
        }
        if(pattern.equals(str)){
            return 0;
        }
        // 计算模式串的next数组
        int[] next=getNext(pattern);
        // 匹配查找
        int i=pos,j=0;
        while(i<str.length()&&j<pattern.length()){
            if(j==-1||str.charAt(i)==pattern.charAt(j)){
                i++;
                j++;
            }else{
                // j回退
                j=next[j];
            }
        }
        return j==pattern.length()?i-j:-1;
    }

    public int[] getNext(String str){
        int n=str.length();
        if(n==1){
            return new int[]{-1};
        }
        if(n==2){
            return new int[]{-1,0};
        }
        int[] next=new int[n];
        next[0]=-1;
        next[1]=0;
        // k用于记录i-1位置需要回退的位置
        int i=2,k=0;
        while(i<n){
            if(k==-1||str.charAt(i-1)==str.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                // k回退
                k=next[k];
            }
        }
        return next;
    }
}

3.3 NC149 kmp算法

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 计算模板串S在文本串T中出现了多少次
     * @param S string字符串 模板串
     * @param T string字符串 文本串
     * @return int整型
     */
    static int count=0;
    public int kmp (String S, String T) {
        kmp(T,S,0);
        return count;
    }
    
    public void kmp (String s, String p,int pos) {
        if(s==null||p==null){
            return;
        }
        if(s.length()==0||p.length()==0){
            return;
        }
        if(pos<0||pos>=s.length()){
            return;
        }
        int[] next=getNext(p);
        int i=pos,j=0;
        while(i<s.length()&&j<p.length()){
            if(j==-1||s.charAt(i)==p.charAt(j)){
                i++;
                j++;
            }else{
                j=next[j];
            }
            if(j==p.length()){
                count++;
                j=next[j];
            }
        }
    }

    public int[] getNext(String s){
        int n=s.length();
        if(n==1){
            return new int[]{-1};
        }
        if(n==2){
            return new int[]{-1,0};
        }
        int[] next=new int[n+1];
        next[0]=-1;
        next[1]=0;
        int i=2,k=0;
        while(i<=n){
            if(k==-1||s.charAt(i-1)==s.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                k=next[k];
            }
        }
        return next;
    }
}

3.4 KMP算法

import java.util.*;

// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        // 注意 hasNext 和 hasNextLine 的区别
        while (in.hasNextLine()) { // 注意 while 处理多个 case
            String str = in.nextLine();
            String match = in.nextLine();
            List<Integer> ans=kmp(str,match,0);
            if(ans.size()==0){
                System.out.println(-1);
                return;
            }
            for(int i=0;i<ans.size();i++){
                System.out.print(ans.get(i));
                if(i!=ans.size()-1){
                    System.out.print(" ");
                }else{
                    System.out.println();
                }
            }
        }
    }

    public static List<Integer> kmp (String s, String p,int pos) {
        List<Integer> ans=new ArrayList<>();
        if(s==null||p==null){
            return ans;
        }
        if(s.length()==0||p.length()==0){
            return ans;
        }
        if(pos<0||pos>=s.length()){
            return ans;
        }
        int[] next=getNext(p);
        int i=pos,j=0;
        while(i<s.length()&&j<p.length()){
            if(j==-1||s.charAt(i)==p.charAt(j)){
                i++;
                j++;
            }else{
                j=next[j];
            }
            if(j==p.length()){
                ans.add(i-j);
                j=next[j];
            }
        }
        return ans;
    }

    public static int[] getNext(String s){
        int n=s.length();
        if(n==1){
            return new int[]{-1,0};
        }
        int[] next=new int[n+1];
        next[0]=-1;
        next[1]=0;
        int i=2,k=0;
        while(i<=n){
            if(k==-1||s.charAt(i-1)==s.charAt(k)){
                next[i]=k+1;
                k++;
                i++;
            }else{
                k=next[k];
            }
        }
        return next;
    }
}

参考链接https://www.zhihu.com/question/21923021/answer/769606119,这位博主关于next数组计算讲的很清晰!

你可能感兴趣的:(数据结构与算法,算法,java,开发语言,数据结构)