문자열 일치 문제를 해결하기 위한 무차별 대입 일치 또는 KMP 알고리즘

1. 문자열 매칭 문제

  • 문자열 S와 문자열 T가 주어지면 S에서 T의 위치를 ​​쿼리하는 것은 문자열 일치 문제입니다.
    여기에 이미지 설명을 삽입하세요

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이면 일치가 성공한 것입니다.
    여기에 이미지 설명을 삽입하세요

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 알고리즘을 사용하고 문자열의 접두어와 접미어 중 가장 긴 공통 하위 문자열을 사용하여 불필요한 잘못된 일치 작업을 줄여 일치 속도를 향상시킬 수 있습니다.
    여기에 이미지 설명을 삽입하세요

2.2.1 알고리즘 단계

여기에 이미지 설명을 삽입하세요

2.2.2 다음 배열 계산

  • 각 위치에 해당하는 접두사와 접미사의 최대 공통 길이를 계산하고 최대 공통 길이 표를 얻습니다.
  • 최대 길이를 오른쪽으로 1비트 이동하고 첫 번째 위치를 -1로 채웁니다(첫 번째 위치에서 시작하는 데 0이 필요한 경우 이 항목의 다음 배열 요소에 1을 추가하면 됩니다).
    여기에 이미지 설명을 삽입하세요
    여기에 이미지 설명을 삽입하세요

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 Lituo 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 Leetour 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 링크를 참조하세요 . 이 블로거는 다음 배열 계산에 대해 매우 명확합니다!

추천

출처blog.csdn.net/qq_43665602/article/details/132415349