初步思路
这是一道难度为简单的题,所以不熟悉的话可能第一反应就是朴素匹配的算法。但因为考研的时候学过数据结构,所以我第一反应是应该使用KMP算法,并私以为难度不应该归为简单,毕竟KMP算法代码虽然比较简单,但理解起来还是比较难。下图为LeetCode上的提交记录。
神奇的是在这个图片中,最上面的为朴素匹配算法,最下面为KMP算法。大家觉得更高级的算法竟然不如最直接暴力的算法????
LeetCode上的评分机制参考一下就行了,大家也不必深究,应该是它上面提供的评分算例比较简单,体现不出KMP算法的优势。中间一些解答错误,便是因为我觉得朴素匹配算法非常简单,没有认真调试,中间有个容易忽略的问题,后文我会提到。
朴素匹配算法
由于朴素算法便于理解,并可作为KMP算法的引子,所以先介绍这种算法。先直接上代码。
class Solution {
public:
int strStr(string haystack, string needle) {
if(needle.empty())
return 0;
int i=0;//源串
int j=0;//子串
int k=i;
int len1=haystack.size();
int len2=needle.size();
while(i<len1&&j<len2) {
if(haystack[i]==needle[j]) {
i++;
j++;
} else {
i=k++;//用k标识i起始位置
j=0;
}
}
if(j>=len2) {
return i-j;
}
return -1;
}
};
haystack为作为模版的源串,needle为待匹配的子串。实现逻辑就是从haystack的一个字符开始,与needle的第一个字符比较,如果相同则haystack的第二个字符与needle的第二个字符进行比较,若不同则haystack移动到haystack的第二个字符,重新开始与needle的第一个字符比较。如此循环下去,直到找到完全匹配的位置,或是haystack已经搜索完毕,但还没找到匹配的位置时结束。
但这种朴素匹配算法的时间复杂度为O(mn),因为中间可能有些比较可以跳过,但是它都得按顺序一一的比较过去(比如ABBBBBBABA中去找ABA)。而接下来介绍的KMP算法就可以减少无用的比较,直接跳到有效的位置。KMP算法的时间复杂度为O(m+n)。
需要注意的是:k用来标记当前源串i的起始位置,否则i遍历到后面会丢失其开始的位置。而j不需要被记录,因为若匹配失败,j都是从头开始。(提交失败的三次都是因为此原因被我忽略。)
KMP算法
KMP算法则是对上述算法的一个改进。它首先求出一个NEXT数组,其指明若发生匹配失败该跳到哪个位置开始重新进行匹配;然后利用NEXT数组对上述算法进行改进。上述算法需要按顺序一个一个往后匹配,而KMP利用NEXT数组直接跳到有效的位置。
NEXT数组
vector<int> getnext(string str)
{
int len=str.size();
vector<int> next;
next.push_back(-1);//next数组初值为-1
int j=0,k=-1;
while(j<len-1)
{
if(k==-1||str[j]==str[k])//str[j]后缀 str[k]前缀
{
j++;
k++;
next.push_back(k);
}
else
{
k=next[k];
}
}
return next;
}
NEXT数组的求解目的主要就是求子串中前缀码和后缀码最大的重合情况。(如ABBAB,ABBBA。)
【KMP算法易懂版-天勤率辉2020全程班公开一期讲解】指路该视频,简单易懂地介绍了NEXT数组的手动求解方法。
在理解了手动求法后,求NEXT数组的代码就很好理解了。
NEXT数组都是从前往后进行,所以求得next[i]后next[i+1]的求值可以分两种情况讨论:
- 若pi等于pj,则next[i+1]=j+1;因为j为当前最长的前后缀码重合情况,若pi等于pj,则相当于在当前的情况下加1。
- 若pi不等于pj,只需将j赋值为next[j],再对pi和pj进行比较。
利用NEXT数组改进朴素匹配算法
得到NEXT数组后,对朴素匹配算法中按顺序依次后挪改为根据NEXT数组寻找下一个位置即可。
int strStr(string haystack, string needle) {
if(needle.empty())
return 0;
int i=0;//源串
int j=0;//子串
int len1=haystack.size();
int len2=needle.size();
vector<int> next;
next=getnext(needle);
while((i<len1)&&(j<len2))
{
if((j==-1)||(haystack[i]==needle[j]))
{
i++;
j++;
}
else
{
j=next[j];//获取下一次匹配的位置
}
}
if(j==len2)
return i-j;
return -1;
}