KMP

KMP算法

1. KMP算法原理

自己读了一遍,发现好多只有自己能看懂。散了吧

1.1 next[]

给定一个数组arr,求它的next[]数组

含义:next[i]表示数组arr第i个元素之前的字符串中,最长的公共前缀后缀的长度。

注:next[0] = -1 next[1] = 0

例子

char[] arr = new char[]{'a', 'b', 'a', 'b', 'c', 'a', 'b', 'a', 'b', 'a', 'k'};

在这里插入图片描述

i=3时,arr第3个元素之前的字符串中,最长前缀为anext[3]==1

  1. 如果继续往下判断,即判断next[4]的值为多少,只需判断当前最长前缀的下一个元素是否与arr[3]相等,而最长前缀的下一个元素的下标为next[i-1],用代码表示即为arr[next[i - 1]] == arr[i -1]

  2. 假设要判断next[9]的值,此时next[9-1]==3arr[9-1]=='b',根据上述分析,需要判断arr[next[i-1]]是否与arr[i-1]相等,即arr[3]是否等于arr[8],相等,则说明i == 8时的最长前缀再加上一个后面的字符,构成了i == 9的最长前缀。

  3. 假设要判断next[10]的值,现已构成的最长前缀为ababnext[9]==4,看arr[4]是否等于arr[9]
    在这里插入图片描述
    发现不等于,且arr[4]前面还有字符,可以继续向前比较。

    接下来找倒数第二长前缀,倒数第二长前缀为ab,其下一个元素为arr[2],接下来比较arr[2]是否等于arr[9]
    在这里插入图片描述
    发现相等,则next[10]为倒数第二长前缀的长度+1。

  4. 如果倒数第二长前缀也不满足,还继续向前找,直到发现第一个元素也不与它相等,则其next值为0。

求next数组的代码如下

public static int[] nextArr(char[] str){
    int len = str.length;
    if(len == 1){
        return new int[]{-1};
    }
    int[] next = new int[len];
    next[0] = -1;
    next[1] = 0;

    //求next[i],看str[i-1]与str[next[i-1]]是否相等,不等,往前判断
    //刚开始时,i==2,next[i-1]==0,故设置cn=0;
    int cn = 0;//最长前缀的下一个元素的下标。
    //先跟最长前缀的下一个元素比较,不相等,再跟第二长前缀的下一个元素比较......
    for(int i = 2; i < len; i++){
        if(str[i - 1] == str[cn]){
            cn++;
            next[i] = cn;
            i++;
        }else if(cn > 0){//即使不匹配,但是当前位置不是数组的首部,说明前面还有字符,继续比较
            //这里为什么不设置判断条件为cn>=0? 因为如果cn==0,在上面的if语句中,
            //判断过str[i-1]!=str[0],说明字符串第一个字符都不与str[i-1]相等,直接得出next[i]==0,
            cn = next[cn];
        }else{//cn==-1,说明前面不可能有字符与str[i-1]相等了
            next[i] = 0;
            i++;
        }
    }
    return next;
}

1.2 算法流程

为每个字符串设置一个遍历指针,从头依次开始比较

如下例,当i=4,j=4时,对应的元素不相等。
在这里插入图片描述
此时next[j]==2,只需要arr2向后移动,让j跳到next[j]的位置上
在这里插入图片描述
此时依旧不相等。j继续向前跳。由j=next[j]得出j值为0。故arr[4]与arr2[0]比较,还不相等。由j=next[j]得出j值为-1。说明arr2的第一个元素与arr[4]不匹配,只能再看arr2的第一个元素与arr[5]匹配不匹配

下面再举另外一个例子。如果next[j]==-1,说明arr2的第一个元素都不匹配,arr2向后移动1,即i++

如下图例子所示
在这里插入图片描述
然后一直往后移动,直到i=4,j=4时,才第一次匹配成功

代码

public static int function(String str1, String str2){
    char[] arr1 = str1.toCharArray();
    char[] arr2 = str2.toCharArray();
    int[] next = nextArr(arr2);
    int len1 = str1.length();
    int len2 = str2.length();
    int i = 0, j = 0;//i遍历arr1, j遍历arr2;
    while(i < len1 && j < len2){
        if(arr1[i] == arr2[j]){//arr2中的元素与arr1中的相匹配,都不移动,接着向下遍历
            i++;
            j++;
        }else{
            if(next[j] == -1){//arr2中第一个元素与arr1不匹配,只有arr2向后移动
                i++;
            }else{//arr2不是向右移动1,那是笨方法,而是使j=next[j],让j向后跳
                j = next[j];
            }
        }
    }
    return j == len2 ? i - j : -1;
}

1.3 完整代码

public class Solution_KMP {
    public static int function(String str1, String str2){
        char[] arr1 = str1.toCharArray();
        char[] arr2 = str2.toCharArray();
        int[] next = nextArr(arr2);
        int len1 = str1.length();
        int len2 = str2.length();
        int i = 0, j = 0;//i遍历arr1, j遍历arr2;
        while(i < len1 && j < len2){
            if(arr1[i] == arr2[j]){//arr2中的元素与arr1中的相匹配,都不移动,接着向下遍历
                i++;
                j++;
            }else{
                if(next[j] == -1){//arr2中第一个元素与arr1不匹配,只有arr2向后移动
                    i++;
                }else{//arr2不是向右移动1,那是笨方法,而是使j=next[j],让j向后跳
                    j = next[j];
                }
            }
        }
        return j == len2 ? i - j : -1;
    }

    public static int[] nextArr(char[] str){
        int len = str.length;
        if(len == 1){
            return new int[]{-1};
        }
        int[] next = new int[len];
        next[0] = -1;
        next[1] = 0;

        //求next[i],看str[i-1]与str[next[i-1]]是否相等,不等,往前判断
        //刚开始时,i==2,next[i-1]==0,故设置cn=0;
        int cn = 0;
        for(int i = 2; i < len; i++){
            if(str[i - 1] == str[cn]){
                cn++;
                next[i] = cn;
                i++;
            }else if(cn > 0){//即使不匹配,但是当前位置不是数组的首部,说明前面还有字符,继续比较
                //这里为什么不设置判断条件为cn>=0? 因为如果cn==0,在上面的if语句中,判断过str[i-1]!=str[0],说明字符串第一个字符都不与str[i-1]相等,直接得出next[i]==0,
                cn = next[cn];
            }else{//cn==-1,说明前面不可能有字符与str[i-1]相等了
                next[i] = 0;
                i++;
            }
        }
        return next;
    }

    public static void main(String[] args){
        //......
    }
}

2. KMP算法应用

2.1 给定一个字符串,构造另一个字符串

题意:给定一个字符串,往该字符串后面添加元素,使得最后的字符串包含两个不同的原字符串,且要求要添加的元素最少

例子:原字符串:abcabc,在其后再添加一个3个元素,分别是a,b,c,最终得到的字符串为abcabcabc。改字符串相等的最长前缀和最长后缀为abcabc,即原字符串。

思路:求原字符串的next数组,假设此数组长度为n,且该数组最长前缀与后缀长度为len,从最长后缀开始,补齐原字符串即可。

如原字符串abcdeab,此时相等的前后缀长度为2,后缀为ab,则再补上cdeab即可。

2.2 判断一棵树是否为另一棵树的子树。

题意大概是如下的样子
在这里插入图片描述
思路:将两棵二叉树序列化,然后判断一个是否为另一个的子串。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值