kmp算法题解

题目是leetcode上的Implement strStr(),题目大意就是在字符串haystack中寻找字符串needle的是否存在,并返回位置。
地址是:
https://leetcode.com/problems/implement-strstr/,可以用来训练一下KMP算法的实现。

学习KMP算法的过程中,参考了这篇文章:
http://blog.csdn.net/yutianzuijin/article/details/11954939/

KMP算法的核心原理在于:传统的字符串匹配,一旦发生失配,那么模式字符串直接回退到最开始。但是在KMP算法中,我们根据模式字符串的特征,在失配的时候,根据已有信息尽可能少的回退。

这个信息就是:模式字符串的最长公共前后缀。比如说一个长度为4的字符串abab,那么最长公共前后缀就是ab,从左边数有个ab,右边也有个ab,长度为2。一旦匹配到最右边的b后发生失配,比如模式:ababc,文本:ababd,因为模式长度为4的部分左右对称,所以不需要全部回退,回退到左边的ab即可。
这个信息也就是存在最长公共前后缀中,我们用一个next数组来表示。比如next[4] = 2,就是长度为2的字符串最长前后缀值为2,于是回退到2即可,也就是ab后的下一个字符。

next数组计算方法:引用参考文章中的说明:

next数组计算
理解了kmp算法的基本原理,下一步就是要获得字符串f每一个位置的最大公共长度。这个最大公共长度在算法导论里面被记为next数组。在这里要注意一点,next数组表示的是长度,下标从1开始;但是在遍历原字符串时,下标还是从0开始。假设我们现在已经求得next[1]、next[2]、……next[i],分别表示长度为1到i的字符串的前缀和后缀最大公共长度,现在要求next[i+1]。由上图我们可以看到,如果位置i和位置next[i]处的两个字符相同(下标从零开始),则next[i+1]等于next[i]加1。如果两个位置的字符不相同,我们可以将长度为next[i]的字符串继续分割,获得其最大公共长度next[next[i]],然后再和位置i的字符比较。这是因为长度为next[i]前缀和后缀都可以分割成上部的构造,如果位置next[next[i]]和位置i的字符相同,则next[i+1]就等于next[next[i]]加1。如果不相等,就可以继续分割长度为next[next[i]]的字符串,直到字符串长度为0为止。由此我们可以写出求next数组的代码(Java版):

    public int[] getNext(String b)
    {
        int len=b.length();
        int j=0;
        int next[]=new int[len+1];//next表示长度为i的字符串前缀和后缀的最长公共部分,从1开始
        next[0]=next[1]=0;
        for(int i=1;i<len;i++)//i表示字符串的下标,从0开始
        {
        //j在每次循环开始都表示next[i]的值,同时也表示需要比较的下一个位置。 这句注释一定要结合上面的说明理解,理解了KMP算法基本也就搞定了。
            while(j>0&&b.charAt(i)!=b.charAt(j))j=next[j];
            if(b.charAt(i)==b.charAt(j))j++;
            next[i+1]=j;
        }
        return next;
    }

这份代码中比较难理解的就是while循环,再举一个例子,比如模式字符串:
ababeababc,当我们计算了前九个字符,有next[9] = 4,也就是前9字符最长公共缀为abab。(注意next[9]表明字符串长度,而4是索引,也就是第五个字符,这块有点绕要注意理解)那么现在开始计算next[10],可以看到 j = next[9] = 4,string[4] != string[9],看这个时候next[4]是什么,是左边abab字符串的最长公共缀ab!由于abab中左右ab是对称的,这个时候最左边的ab加上一个a,和最右边的ab加上string[9]也就是c,有可能组成新的更长的公共前缀(当然本例中a!=c,所以不存在,继续拆分)。这就是计算next的精髓,通过j=next[j]加速寻找。因为要的到更长的最长公共缀,那么i之前和j之前的字符串必须是对应的。再举个例子 dabdabcdabdabd

 d a b d a b c d a b d a b d
             j             iij处字符不同,回退
       j                   i    ,回退到next[j],字符相同
 d a b d             d a b d    ,生成公共缀next[i+1]=j。
可以看到,左边和右边除了ij位置外用到的字符串,实际上是
d a b d a b  的最长公共缀 d a b,如果没有回退到next[j],即使ij字符相同,剩余的部分也无法匹配。     

有点类似于一个递归的过程,只不过在递归过程中,我们不断移动j指针,使它指向可能存在结果的最长公共前缀的下一个字符,并和当前处理到的字符对比,来计算出新的next值

本题解:

public class Solution {
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0) return 0;
        int[] next = getNext(needle);
        int i = 0, j = 0;
        while(i<haystack.length()&& j < needle.length()){
            if(haystack.charAt(i) == needle.charAt(j)){
                i++;
                j++;
            }else{
                if(j != 0){
                    j = next[j];
                }else {
                    i++;
                }
            }
        }
        if(j == needle.length()) return i - needle.length();
        else return -1;
    }
    public int[] getNext(String b)
    {
        int len=b.length();
        int j=0;
        int next[]=new int[len+1];
        next[0]=next[1]=0;
        for(int i=1;i<len;i++)
        {
            while(j>0&&b.charAt(i)!=b.charAt(j))j=next[j];
            if(b.charAt(i)==b.charAt(j))j++;
            next[i+1]=j;
        }
        return next;
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值