算法训练:重复的子字符串(KMP)java

问题描述
在这里插入图片描述
在用KMP算法实现该问题前先说明以下什么是KMP算法,以及KMP算法的常规实现是怎样的。

KMP算法:
首先先看一组例子,上面为主串,下面为模式串
在这里插入图片描述
在这里插入图片描述

由图中可以看到,模式串的前五个字符和主串的前五个字符是相同的,即匹配的,而第六个开始,主串与模式串的字符开始不同。那么这个时候,开始看模式串前五位中的最大公共前缀是啥,如下图所示:
在这里插入图片描述
这个时候我们可以发现,模式串中最长的可以与主串相匹配的子串为GTG,因此接下来,我们可以移动模式串,将模式串中匹配的最大前缀子串向右移动,移到和主串中的三个相互匹配的最长可匹配后缀子串同一个位置。
在这里插入图片描述
然后继续比较公共前后缀之后的字符,发现A和T依旧不匹配,按照上一步的思想,应该继续在GTG三个字符中找最长公共前后缀,此时,只有G字符满足条件,因此将模式串中的第一个G字符向右移动,移到和主串中的第三个字符G相同的位置。
在这里插入图片描述
此时可以看到,匹配好的模式串的前方已经没有前缀了,主串和模式串的首位已经对其,现在的情况和开始的时候就是一样的了,接下来继续按照相同的方法对模式串进行移动匹配即可。

但是,在实际的计算机代码中是无法实现抽象的模式串移动的,那么,我们该如何间接的表示这种移动呢?

在KMP算法中,采用了一个next[ ]数组,来存放模式串中每个字符前对应的最大公共前缀的长度。一般情况下,模式串的前两位的next数组值一般设置为-1,0或者0,1;为了方便理解,在这里设置为-1和0;

例如:字符串 "abcabcaaa"的next[ ]数组的值为[-1,0,0,0,1,2,3,4,0]
具体的手工算法可以参考一下的一个b站视频:
https://www.bilibili.com/video/BV12J411m74v

在代码中通常设置一个大小不超过模式串长度的辅助位置指针变量k来间接的代替移动的功能。当需要移动的时候,k就指向next[k]的位置。

以上就是KMP算法的基本思想,也是最常规的一种情况。

接下来按照C语言与Java来分别实现一下KMP的代码

C语言:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct
{
    
    
	char *str;
	int length;
	int maxlength;
}DString;
void getnext(DString T,int next[]);
int KMP(DString S,DString T,int next[]);
void Initiate(DString *S,int max,char *string);
void main()
{
    
    
	//定义主串
	DString S;
	//定义子串
	DString T;
	int next[100];
	int index;
	//以下是初始化操作
	Initiate(&S,100,"foiahfohaofihqoifhakfjoqwifjfasofiqhfklafhqhfoiqfhaf");
	Initiate(&T,5,"wifjf");
	getnext(T,next);
	index=KMP(S,T,next);
	printf("%d \n",index);
	system("pause");
}
//S为主串,T为模式串
void getnext(DString T,int next[])
{
    
    
	int j=1,k=0;
	next[0]=-1;
	next[1]=0;
	while(j<=T.length-1)
	{
    
    
		if(T.str[j]==T.str[k])
		{
    
    
			next[j+1]=k+1;
			j++;
			k++;
		}
		else if(k==0)
		{
    
    
			next[j+1]=0;
			j++;
		}
		else k=next[k];
	}
}
int KMP(DString S,DString T,int next[])
{
    
    
	//定义辅助变量i遍历主串,辅助变量k遍历字串
	//由于辅助变量k对应了next数组里的值,因此可以将k的值简单的理解为位置
	//再通过位置来反向理解与继续接下来的操作
	int i=0,k=0,v;
	while(i<S.length&&k<T.length)
	{
    
    
		//k=-1表示指向子串的指针停留在首位置,所以接下来的操作就是将两个指向主串和子串的指针向后移动,进行位移
		if(k==-1||S.str[i]==T.str[k])
		{
    
    
			i++;
			k++;
		}
		//如果两个指针对应位置的值不相等,则将k指针返回到最大公共前缀的位置,最差的结果就是再次返回到起始位置
		else k=next[k];
		//当k指针的位置超过了子串的长度的时候,就将匹配到的位置下标返回,否则返回-1表示没有找到
	}
	if(k==T.length)
		v=i-T.length;
	else v=-1;
		return v;
}
void Initiate(DString *S,int max,char *string)
{
    
    
	int i;
	S->str= (char *)malloc(sizeof(char)*max);
	S->maxlength=max;
	S->length=strlen(string);
	for(i=0;i<S->length;i++){
    
    
		S->str[i]=string[i];
	}
}

运行结果:

扫描二维码关注公众号,回复: 12420437 查看本文章

在这里插入图片描述
Java语言:

class Solution {
    
    
    public boolean repeatedSubstringPattern(String s) {
    
    
        
    }
    public int KMP(int start,String S,String T,List<Integer> list){
    
    
        int i=start,k=0;
        while(i<S.length()&&k<T.length()){
    
    
            if(k==-1||S.charAt(i)==T.charAt(k)){
    
    
                i++;
                k++;
            }
            else k=list.get(k);
        }
        if(k==T.length()-1){
    
    
            return i-T.length();
        }
        else return -1;
    }
    public List<Integer> getnext(String pattern){
    
    
        List<Integer> list =new ArrayList<>();
        int j=1,k=0;
        list.add(-1);
        list.add(0);
        while(j<pattern.length()-1)
        {
    
    
            if(pattern.charAt(j)==pattern.charAt(k)){
    
    
                list.add(k+1);
                j++;
                k++;
            }
            else if(k==0){
    
    
                list.add(0);
                j++;
            }
            else k=list.get(k);
        }
        return list;
    }
}

以上就是KMP算法的基本思想与代码实现,至少课本上的最基本的原理是这样的。

下面就是今天的这道题目所运用的KMP算法的实际提现,其中的next[ ]数组与课本上所讲的前两位设置为-1,0又有些许的区别,且最后面的一位设置为0或者是-1(-1的出现和一开始的next数组初始化有关)。

现在开始正式分析这题:
问题分析:该题给我们的函数传递了一个字符串,让我们判断该字符串是否是由一组其对应的子串重复连接组成的,是则返回布尔值true,否则返回布尔值false。

我们假定该字符串是s,实际上,当一个字符串是由其一个子串重复连接实现的时候,s字符串就可以看作是字符串 s+s(s在s+s中并非从首位置开始,也就是并不是简单的看作是在s的后方直接连接了一个s字符串)的一个子串。
所以就可以采用KMP算法,在s+s中,从非首位置开始,把s字符串作为模式串(可以理解为”返主为客“),在s+s字符串中查找,如果找到了,那么就说明s字符串是由其一个子串相继连接组成的。

关于这种思想的正确性的证明就不展开论述了,过程也比较复杂,就简单的在下方放一个数学证明过程的图,有兴趣的朋友们可以自己研究一下:
在这里插入图片描述
在这里插入图片描述
那么接下来就是用java来实现该问题的解决方案,上代码!

这是Leetcode上的官方解法:

class Solution {
    
    
    public boolean repeatedSubstringPattern(String s) {
    
    
        return kmp(s + s, s);
    }

    public boolean kmp(String query, String pattern) {
    
    
        int n = query.length();
        int m = pattern.length();
        int[] fail = new int[m];
        Arrays.fill(fail, -1);
        for (int i = 1; i < m; ++i) {
    
    
            int j = fail[i - 1];
            while (j != -1 && pattern.charAt(j + 1) != pattern.charAt(i)) {
    
    
                j = fail[j];
            }
            if (pattern.charAt(j + 1) == pattern.charAt(i)) {
    
    
                fail[i] = j + 1;
            }
        }
        int match = -1;
        for (int i = 1; i < n - 1; ++i) {
    
    
            while (match != -1 && pattern.charAt(match + 1) != query.charAt(i)) {
    
    
                match = fail[match];
            }
            if (pattern.charAt(match + 1) == query.charAt(i)) {
    
    
                ++match;
                if (match == m - 1) {
    
    
                    return true;
                }
            }
        }
        return false;
    }
}

本人的代码:

class Solution {
    
    
    public boolean repeatedSubstringPattern(String s) {
    
    
        List<Integer> list =new ArrayList<>();
        list=getnext(s);
        int v=KMP(1,s+s,s,list);
        if(v!=-1&&v!=s.length()){
    
    
            return true;
        }
        return false;
    }
    public int KMP(int start,String S,String T,List<Integer> list){
    
    
        int i=start,k=0;
        while(i<S.length()&&k<T.length()){
    
    
            if(k==-1||S.charAt(i)==T.charAt(k)){
    
    
                i++;
                k++;
            }
            else k=list.get(k);
        }
        if(k==T.length()){
    
    
            return i-T.length();
        }
        else return -1;
    }
    public List<Integer> getnext(String pattern){
    
    
        List<Integer> list =new ArrayList<>();
        int j=1,k=0;
        list.add(-1);
        list.add(0);
        while(j<pattern.length()-1)
        {
    
    
            if(pattern.charAt(j)==pattern.charAt(k)){
    
    
                list.add(k+1);
                j++;
                k++;
            }
            else if(k==0){
    
    
                list.add(0);
                j++;
            }
            else k=list.get(k);
        }
        return list;
    }
}

当然,这道题可以直接用String类的方法indexof来解决,代码在下方,本文只讨论kmp算法在具体题目中的应用。
在这里插入图片描述

class Solution {
    
    
    public boolean repeatedSubstringPattern(String s) {
    
    
    //在s+s字符串中从1位置开始搜寻s字符串,搜寻到返回第一次出现的位置所在的索引值
        return (s + s).indexOf(s, 1) != s.length();
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_45394002/article/details/108265475
今日推荐