kmp以及算法中如何使用kmp去解决问题

一、看一个问题

两个字符串abcabcdeanc和abcd,我们判断后面的字符串是否时前面字符串的子串,如果是返回数组下标,否则返回-1。

暴力解法

    public static int matchStr(String s,String m){
        if(s == null || m ==  null || s.length() < 1 || s.length() < m.length()){
            return -1;
        }
        char[] str1 = s.toCharArray();
        char[] str2 = m.toCharArray();
        int sindex = 0;
        int mindex = 0;
        while(sindex < str1.length && mindex < str2.length){
            if(str1[sindex] == str2[mindex]){
                sindex++;
                mindex++;
            }else{
                sindex = sindex - mindex + 1;
                mindex = 0;
            }
        }
        return mindex == str2.length ? sindex - mindex : -1;
    }

不多解释了这个算法,时间复杂度O(m * n)。

KMP算法

对于暴力解法,在匹配失败后回退一直是挨个字符遍历,没有用到字符串的特性。而kmp利用next数组解决了这一点。

看图(要匹配的字符串str2):

如果当前i = 6指向最后一个字符,那么next[i]表示的就是前0 - i-1字符的最大前缀和后缀长度(前缀和后缀相同)。

前缀:

后缀:

同时呢,计算这个前缀后足长度的时候,这个长度不允许等于i,也就是前缀和后缀不能是整个前面的字符串(如果没有这个要求,那么前缀和后缀都必然是前面字串)。

那么如何机算next[]呢?初始状态next[0] = -1,next[1] = 0,很好理解,下标0之前的东西根本没有,下标1之前只有一个字符,但是这个字符不允许用,那么就是0。我们来看建立的过程:

设置一个变量cn,这个很重要,表示最长前缀(前缀和后缀相等)的长度,同时也是前缀的下一个位置,起初i=2。

比如 i = 5,ab是最长前缀那么cn就是红色箭头所指,cn = 2:

 

分为下面几种情况:

(1)如果chars[cn] == chars[i - 1],那么直接next[i] = ++cn;i++

(2)如果不等于,同时cn大于0,也就是说不是指向第一个位置,那么cn = next[cn],相当于拿到了cn之前字符串的最长前缀和后缀的位置的下一个位置。

(3)如果cn == 0,那么直接赋值0。i++。

目前:i = 2,cn = 0。判断i- 1位置和cn位置字符是否相同,不相同。则next[i] = 0。

一直到i = 4。next[i] = 1,表示前面abca的最大前缀和后缀相同的长度是1.这个时候cn = 1,表示要比较下一个字符。

i = 5的时候i - 1和cn = 1的字符相同,那么cn++,同时next[i] = 2。

最后数组如图。

 上面过程没有出现情况2,对于情况2,cn = next[cn],以前面最长前缀为字符串,找到这个字符串的最长前缀位置,然后再比较。

   public static int[] getNextArr(char[] chars){
        if(chars.length < 2){
            return new int[]{-1};
        }
        int[] next = new int[chars.length];
        next[0] = -1;
        next[1] = 0;
        int i = 2;
        //跳到的位置
        int cn = 0;
        while(i < next.length){
            if(chars[i - 1] == chars[cn]){
                next[i++] = ++cn;
            }else if(cn > 0){
                //如果不相等,跳转前一个最长前缀
                cn = next[cn];
            }else{
                //到了第一个字符串
                next[i++] = 0;
            }
        }
        return next;
    }

之后明确了这个数组,我们来看一下kmp

    public static int getIndexOf(String s,String m){
        if(s == null || m ==  null || s.length() < 1 || s.length() < m.length()){
            return -1;
        }
        char[] str1 = s.toCharArray();
        char[] str2 = m.toCharArray();
        int sindex = 0;
        int mindex = 0;
        int[] next = getNextArr(str2);
        while(sindex < str1.length && mindex < str2.length){
            if(str1[sindex] == str2[mindex]){
                sindex++;
                mindex++;
            }else if(next[mindex] == -1){
                sindex++;
            }else{
                mindex = next[mindex];
            }
        }
        return mindex == str2.length ? sindex - mindex : -1;
    }

先看主体思路,s字符串是不回退的,只通过next改变m字符串的下标。这里分为三种情况:

(1)如果发现相等,同时数组下标移动。

(2)如果发现m字符串第一个字符与s字符串不相同,那么s字符串下标移动。

(3)如果发现不匹配并且不是第一个字符,那么m字符串回退。如图:

 

然后开始匹配!!!发现不同

之前暴力方法的移动是这样的:

kmp:通过查找next数组。next[6] = 3,那么mindex = 3。然后再比较。

最后直到查找完毕。

二、kmp算法的应用

1、京东笔试题

题目:首先有一个原始串,例如str = "abcabc",要搞成一个新的字符串(其中饱汉两个原始串),新的字符串="abcabcabc",要求长度最短。

我们可以这样搞!我们找出这个字符串"abcabc"的最长前缀len,也就是abc,然后令i = len 遍历到结束,添加字符。

public static int getNextArr(char[] chars){
        if(chars.length < 2){
            return -1;
        }
        int[] next = new int[chars.length + 1];
        next[0] = -1;
        next[1] = 0;
        int i = 2;
        //跳到的位置
        int cn = 0;
        while(i < next.length){
            if(chars[i - 1] == chars[cn]){
                next[i++] = ++cn;
            }else if(cn > 0){
                //如果不是第一个
                cn = next[cn];
            }else{
                //到了第一个字符串
                next[i++] = 0;
            }
        }
        return next[next.length - 1];
    }

    public static void main(String[] args) {
        String string = "abcabca";
        char[] arr = string.toCharArray();
        int len = getNextArr(string.toCharArray());
        System.out.println(len);
        StringBuffer buffer = new StringBuffer();
        buffer.append(string);
        for(int i = len ;i < arr.length;i++){
            buffer.append(arr[i]);
        }
        System.out.println(buffer.toString());
    }

2、判断一棵树中是否包含一棵子树

首先对左边二叉树进行序列化用#表示null,用_分割,先序遍历生成序列(树唯一):

1_2_4_#_#_#_3_#_#

再对右边:

2_4_#_#_#。

我们只需要用kmp判断子树的字符串是否在其中就可以了

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值