KMP算法的应用场景:字符串模式匹配
字符串匹配问题一般会给出一个目标字符串(主串)和一个待匹配字符串(模式串),要做的工作是看在主串中查找是否出现过模式串,也就是C语言中的strStr()函数。
暴力解法:
通常的暴力解法是从主串和模式串的第一个字符开始比较,如果相等则继续比较主串和模式串的下一个字符, 如果不相等则将模式串的第一个字符和主串的下一个字符开始比较,若模式串被比较完毕,则匹配成功,或主串被比较完且模式串未匹配完,则比较失败。
KMP算法思想:
KMP算法即使在比较的过程中遇到字符不一致时,其已经记录了主串中该字符之前的,与模式串的开头部分相同的字符个数(即next数组),这样就可以直接将模式串相同部分的后一个字符与主串的该字符继续比较,避免了从模式串的开头开始进行匹配。
所以KMP算法实现的核心就是next数组的构造。
next数组
构造next数组之前,先来说一下前缀和后缀的概念。
前缀、后缀:
前缀:对一个字符串来说,前缀是包含其第一个字符但不包括最后一个字符,能组成的全部的字符串的集合。例如:
字符串:"abcab"
前缀: {"a", "ab", "abc", "abca"}
后缀:后缀是不包含字符串的第一个字符但包括最后一个字符,能组成的全部的字符串的集合。
字符串:"abcab"
后缀: {"b", "ab", "cab", "bcab"}
next数组:
模式串中,对于每一个字符及其之前的字符所组成的字符串,拥有的最长相同前后缀的长度即为next数组中记录的对应的数字。例如:
字符串: "aabaaf"
next数组:{0,1,0,1,2,0}
解释:
"a":无前后缀,最长相同前后缀长度为0;
"aa":最长相同前后缀为"a",长度为1;
"aab":没有相同前后缀,为0;
"aaba":最长相同前后缀为"a",长度为1;
"aabaa":最长相同前后缀为"aa",长度为2;
"aabaaf":没有相同前后缀,为0。
KMP算法匹配过程
① 假设有两个指针i,j :i 指向主串的第一个字符,j 指向模式串的第一个字符。
② 将两个字符进行比较,若相同则两个指针各自向后移动一位,即:i++;j++;后继续比较。
③ 若不同,则指向主串的指针 i 不变,指向模式串的指针则先查找其前一位在next数组中对应的数字,即:next[ j - 1 ] , 然后将指针指向模式串的这一个位置,即:j = next[ j - 1 ] 。
④ 若 j 一直回退到模式串首位,但依然与主串当前字符不同,则主串指针往后移动一位即:i++。
⑤ 若 j 已经指向模式串最后一位的后一位,则说明已经找到,可以退出循环。如果 i 指向主串的最后一位的后一位,而 j 还没走完,则说明不存在匹配字段。
例题代码实现 :
LeetCode 28. 找出字符串中第一个匹配项的下标
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 :
示例1:
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例2:
输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1
题解(C++):
class Solution {
public:
//模式串next数组的创建
void getNext(int* next, string needle){
//前缀的末尾
int j = 0;
//首位无前后缀
next[0] = 0;
for(int i = 1; i < needle.length(); i++)//后缀的末尾
{
//指针回退
while(j > 0 && needle[j] != needle[i])
j = next[j - 1];
//前缀和后缀末尾相同则计数
if(needle[j] == needle[i])j++;
next[i] = j;
}
}
int strStr(string haystack, string needle) {
int size = needle.length();
int next[size];
getNext(next,needle);
int j = 0;
// i 为主串的指针
for(int i = 0; i < haystack.length(); i++)
{
//字符不相同时模式串指针回退直到相同或指向模式串首位,KMP过程3,4
while(j > 0 && needle[j] != haystack[i])j = next[j - 1];
//字符相同则各自向后移动一位(i 在for中移动),KMP过程2
if(needle[j] == haystack[i])j++;
//模式串指针指向模式串最后一位的后一位,匹配完成,返回,KMP过程5
if(j == size)
return i - size + 1;
}
//没找到
return -1;
}
};