字符串匹配——KMP算法(leetcode 28)
1.前言
在一个字符串中寻找是否包含目标字符串,实现这个要求并不难,遍历文本的每个字符串,如果和目标字符串的第一个匹配,就把匹配的字符后移一位继续对比,直到不匹配,然后将文本的指针后移一位,继续对比即可。但是这样的暴力匹配最坏情况的时间复杂度为O(n*m),而KMP算法可以将其复杂度降低到O(n+m),减少重复对比次数。
2.正文
在学习KMP算法时,我翻阅了不少博客,但是五花八门的KMP介绍让我有点迷糊,有时候似乎看懂了,但是换一个例子我似乎不明白应该如何构造next数组,直到看了这个视频,对于KMP不懂的小伙伴们可以看看这个视频,视频大概在4-12分钟中讲解了如何构造利用next数组,并如何使用next数组减少重复对比。
比起那些干涩的博客,这个视频提供了很好的例子说明如何在O(n)级数下构造next数组(利用已经求得的前面部分next来构造后面的部分),并且提供了几个例子生动的说明,next数组的作用:记录当前的后缀字串与前缀子串最大匹配长度。
在这里不具体说明,有问题的小伙伴们可以去看看上面的视频,看完之后尝试自己动手写一个kmp算法。下面我会贴出看完这个视频后我自己实现的kmp。
3.实现
以下是KMP算法实现,主要包含kmp函数返回-1(未匹配)或匹配的第一个位置(下标从0开始),本人的next数组下标是从0开始到length-1,还包括一个getNext函数用来构造next数组,没有考虑text以及pattarn的一些异常输入,主要是为了更单纯的实现kmp算法。
1 static int kmp(char[] text,char[] pattarn){ 2 int[] next = new int[pattarn.length]; 3 getnext(pattarn,next); 4 int m = 0;//matchLength 5 for(int i = 0;i<text.length;i++){ 6 while(m>0&& pattarn[m] != text[i]){ 7 m = next[m-1]; 8 } 9 if(text[i] == pattarn[m]){ 10 m++; 11 if(m == pattarn.length){ 12 return i - m+1; 13 } 14 } 15 } 16 return -1; 17 } 18 19 private static void getnext(char[] pattarn, int[] next) { 20 int q = 0;//q代表前一个字符前后缀能匹配的最大长度 21 for(int i = 1;i<pattarn.length;i++){//next[0] = 0,因此从1开始 22 while(q > 0 && pattarn[q] != pattarn[i]){//递归直到q为0(没有匹配的前缀)或者当前字符与q相等时(不断“递归”查前缀匹配的前一个位置q) 23 q = next[q-1];//如果不相等,如“acad”,i=3,q=1,则q变成next[q-1](q-1是不匹配的前一个位置) 24 } 25 if(pattarn[q] == pattarn[i]){ 26 q++; 27 } 28 next[i] = q; 29 } 30 }
对于更完善的kmp以及入参的异常处理实现在下面贴出,也已经通过leetcode 28 Implement strStr()。其实就是对传入参数进行了一些意外值处理。
1 class Solution { 2 public int strStr(String haystack, String needle) { 3 if (needle.length() == 0) 4 return 0; 5 if (haystack.length() < needle.length()) 6 return -1; 7 8 char[] text = haystack.toCharArray(); 9 char[] pattarn = needle.toCharArray(); 10 int[] next = new int[pattarn.length]; 11 getnext(pattarn,next); 12 int m = 0;//matchLength 13 for(int i = 0;i<text.length;i++){ 14 while(m>0&& pattarn[m] != text[i]){ 15 m = next[m-1]; 16 } 17 if(text[i] == pattarn[m]){ 18 m++; 19 if(m == pattarn.length){ 20 return i - m+1; 21 } 22 } 23 } 24 return -1; 25 } 26 27 private static void getnext(char[] pattarn, int[] next) { 28 int q = 0;//q代表前一个字符前后缀能匹配的最大长度 29 for(int i = 1;i<pattarn.length;i++){//next[0] = 0,因此从1开始 30 while(q > 0 && pattarn[q] != pattarn[i]){//递归直到q为0(没有匹配的前缀)或者当前字符与q相等时(不断“递归”查前缀匹配的前一个位置q) 31 q = next[q-1];//如果不相等,如“acad”,i=3,q=1,则q变成next[q-1](q-1是不匹配的前一个位置) 32 } 33 if(pattarn[q] == pattarn[i]){ 34 q++; 35 } 36 next[i] = q; 37 } 38 } 39 }
在理解了KMP的流程后,赶紧动手实现一下吧。实现完还可以去leetcode测试一下哦!