关于KMP算法

力扣第28题可以用来练手kmp算法。

next数组的实现

先看代码

public int[] kmp(String s) {
        int j = 0;
        int k = -1;
        int[] next = new int[s.length()];
        // 这里是为了后续匹配的时候作为哨兵使用,并且也方便让next[1] = 0
        next[0] = -1;
        while (j < s.length() - 1) {
            if (k == -1 || s.charAt(j) == s.charAt(k)) {
            	// 单个字符前后缀是同一个字母所以第一个字符的next值为0
            	// 此后每成功匹配一个字符,相应的next值就会+1
                next[++j] = ++k;
            } else {
            	// 难点在于匹配失败时为什么k会回到next[k]这里
                k = next[k];
            }
        }
        return next;
    }

当时主要被k=next[k]这里绊了很久。next[k]的意思不仅可以理解为最长相同前后缀的长度,还可以理解为前缀的最后一个字符后面一个字符的索引。k=next[k]可以理解为k由指向最长相同前后缀 中前缀 的末尾改为指向 最长相同前后缀 中的 最长相同前后缀的末尾,在最长前后缀里 寻找最长前后缀进行匹配。假设此时最长相同前后缀是"abab",k原本指向了"abab"后面一个字符去与s[j]匹配,现在指向了"abab"中第二个’a’去与s[j]匹配。目的是省去前面两个字符’a’,‘b’,的匹配,因为此时s[j],前面也有"abab"。有点绕,需要自己画图去理解。
可以看如下分析:

假设当s[k-1]==s[j-1]时,我们假设此时最长相同前后缀为"abab",即s[0]到s[k-1]分别是’a’,‘b’,‘a’,‘b’,
s[j-k]到s[j-1]也是’a’,‘b’,‘a’,‘b’。此时next[j]的值是4,next[k]的值是2。我们可以发现,next[k]表示的是以索引j-1为尾的最长相同前后缀的最长相同前后缀,即"abab"的最长相同前后缀是"ab"。

当s[k] == s[j]时,正常匹配成功,next[j+1]=k+1。

当s[k] != s[j]时,k需要向前回溯,回溯的位置是next[k],如上例子就是k和j匹配完"abab"后,k指向下一个字符比如’e’,j指向下一个字符’a’,匹配失败k要回溯,k回溯到next[k]即回到"abab"的最长前后缀"ab"的后面,用’a’匹配 j 指向的’a’,匹配成功则此时最长前后缀长度为3,可以继续向后匹配,匹配失败则k一直回溯,直到k = next[0] = -1,就回到了最开始的情况。

而若是k回溯到k-1即指向’b’,此时即使假设j指向的下一个字符也是’b’,匹配成功,因为k的前一个字符是’a’,j的前一个字符是’b’,二者最长前后缀从这里开始,长度就变成了1,匹配失败则继续向前回溯直到k=next[k]才会变成上面的情况。可以看到若是匹配成功最长前后缀便会从3变成1,是虚假的最长前后缀,若是失败也要比上面的情况多走好几步才行。

如果一个一个向前回溯的话,就会和暴力匹配字符串一样低效率了,那么为了减少不必要的匹配也需要一个最长前后缀,可以理解为在kmp算法里面使用kmp算法,这也是上面说到最长前后缀的最长前后缀的原因。

因为要避免暴力匹配字符串中的不必要匹配,所以使用kmp算法,利用最长前后缀。而kmp算法中next数组的实现本质上也是字符串的匹配,同样可以利用到最长前后缀减少不必要的匹配。

这里举的例子比较简单,主要是为了方便理解,大家可以自己找一个字符串,它的最长前后缀也拥有最长前后缀,自己按照算法走一遍会清晰很多。比如"ababcababa"。

使用next数组进行字符串匹配

public int strStr(String haystack, String needle) {
        if (needle.length() == 0)
            return 0;
        int[] next = kmp(needle);
        for (int i = 0,j = 0; i < haystack.length() && j < needle.length();) {
            if (j == -1 || haystack.charAt(i) == needle.charAt(j)) {
                i++;
                j++;
            } else {
                j = next[j];
            }
            if (j >= needle.length()) {
                return i - j;
            }
        }
        return -1;
    }

i 是文本串的索引,j 是模式串的索引。
没啥说头,相同两个字符串一起走,不相同 j 往后退,直到退到next[0]也就是 -1,然后继续匹配,i 到头 j 没到头就是失败,j 能到头说明成功。

以上均为个人理解,难免会有不足和错误,希望各位不吝指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值