面试怎么回答KMP算法相关问题
面试官问:你知道什么是KMP算法吗?说说你对KMP算法的理解。
答:KMP算法是用来进行字符串匹配查找的,比如在字符串1中查找是否包含字符串2。核心是先求出Next数组。什么是next数组?我的理解是:
next数组表示的是待查找的字符串的最大公共前后缀中的公共前缀的最后一个字符的下标,知道这个下标,就可以知道当匹配目标字符串出错时,目标字符串的指针怎么回退,而查找段落的指针不用回退,这样遍历一遍查找段落,就可以知道是否存在目标字符串,时间复杂度为O(n)
例如,求目标字符串ababc的next数组,下标从0开始。
比如next[2],2位置为a,则next[2]表示0位置的a(因为aba的最长公共前缀为a),next[2] =0。
比如next[3],3位置为b,则next[3]表示1位置的b(因为abab的最长公共前缀为ab), next[3] = 1。
比如next[4],由于4位置为c,而ababc没有公共前后缀,则next[4]=-1。
那么什么是最长公共前后缀,为什么需要它和next数组?这里我解释下它的原理:
- 比如需查找字符串"ababc",子串ab最长公共前后缀长度为0(因为a和b不等),子串aba最长公共前后缀长度为1(a的长度,首和尾都有a), 字串abab最长公共前后缀长度为2(ab的长度,首和尾都有ab),ababc最长公共前后缀长度为0(首和尾不同)。
- 在子串1“abababc”中查找子串2"ababc",对照看下图:当我们匹配到第五位时,发现a和c不相等,如果没有这个next数组,我们需要重新比对子串1的第2位(指针从第5位回到第2位)和子串2的第1位。有了这个next数组,我们知道错误匹配字母c前面的abab最大公共前后缀长度为2,所以我们可以直接右移子串2两位,比对子串1的还是第五位和子串2的第3位。这样的匹配算法,字串1中的指针不用回退,等于只要遍历一遍字串1就可以查到是否包含字串2,时间复杂度位O(n)
- next数组求法代码为:
public void getNext(String needle,int next[]){
//规定长度为1的字符串没有最长公共前后缀
next[0]=-1;
char []arr=needle.toCharArray();
int strLen=arr.length;
for(int i=1;i<strLen;i++){
int t=next[i-1];
// 下面的while循环表示arr[t+1]!= arr[i],不等时指针就t=next[t]回退,为什么需要回退?
//比如abcaba中前缀abc的c和后缀aba的a不等,但并不代表没有公共前后缀了,如前缀的a和后缀的a相等,
//所以需要回退,t>=0 表示指针还没回退到起点,如果回退到起点了都不等,那只能说明没有公共前后缀了。
while(arr[t+1]!=arr[i]&&t>=0)
t=next[t];
//判断是因相等退出循环,还是到了起点都不等而退出循环
if(arr[t+1]==arr[i])
next[i]=t+1;
else
next[i]=-1;
}
}
求到了next数组后,只要遍历一遍子串1就可以了,如果出现不匹配,则回退子串2的指针。如果字串2已经回退到起点了,则移动字串1到下一个位置继续匹配即可。
实践运用KMP算法:
这是一道LeetCode的面试题目,第28题,题目为:
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
示例 1:
输入: haystack = "hello", needle = "ll"
输出: 2
一种取巧的方法是java中String的一个函数indexOf()可以直接得出结果。
这道题完整的解答代码:
class Solution {
public void getNext(String needle,int next[]){
next[0]=-1;
char []arr=needle.toCharArray();
int strLen=arr.length;
for(int i=1;i<strLen;i++){
int t=next[i-1];
while(arr[t+1]!=arr[i]&&t>=0)
t=next[t];
if(arr[t+1]==arr[i])
next[i]=t+1;
else
next[i]=-1;
}
}
public int strStr(String haystack, String needle) {
int len=needle.length();
if(len==0)
return 0;
int l1=haystack.length();
int l2=needle.length();
int next[]=new int[l2];
getNext(needle,next);
int i=0,j=0;
while(i<l1){
if(haystack.charAt(i)==needle.charAt(j)){
i++;j++;
if(j==l2)
return i-l2;
}
else{
if(j==0)
i++;
else{
//需查找的字符串指针回退
j=next[j-1]+1;
}
}
}
return -1;
}
}