kmp算法用于解决字符串匹配问题。初学者学习中,一些不太成熟的见解。若有错误,请指出。
一、暴力解法
假设文本串haystack(即被匹配字符串)为 aabaabaaf
模式串needle(即匹配字符串)为aabaaf
i指向文本串,j指向模式串
暴力解法就是i和j指向的字符若相等,二者同时++,若不相等,就让i回退到开始位置+1,j回到字符串开始位置,再次匹配。(注意添加flag来实现找到第一个匹配位置后停止寻找)
public int strStr(String haystack, String needle) {
int ans=-1;
boolean flag=false;
for(int i=0;i<haystack.length();i++) {
if(flag) {
break;
}
for(int j=0,k=i;j<needle.length();j++,k++) {
if(k>=haystack.length()||haystack.charAt(k)!=needle.charAt(j)) {
break;
}
if(j==needle.length()-1) {
ans=i;
flag=true;
}
}
}
return ans;
}
优化思路:在暴力解法中,i指针本质上是在不断回头,我们可以让i指针一直向前走(上面的暴力解法中,是k代替i指针回头了)
(注:当然我们也希望j指针也不回头,但是它是要匹配的字符串,总是要试错的,所以只能尽量减少它回头的次数和范围)
如何做到让i一直往前走呢?
我们可以让模式串的指针j移动,让其指向与当前i指针指向位置匹配的地方
为什么可以这样移动呢?
因为模式字符串有相同的部分(没有的话就更简单啦)
可以让文本串中原来要当“尾”的部分现在变成“头”
那移动多少呢?又如何实现呢?
用前缀表实现
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
二、KMP算法
要知道前缀表是什么,首先要清楚前缀和后缀。
1.前缀和后缀
前缀是包含首字母,不包含尾字母的所有子串
如:aabaaf
前缀:a aa aab aaba aabaa
后缀是包含尾字母,不包含首字母的所有子串
如:aabaaf
后缀:f af aaf baaf abaaf
求出最长相等前后缀
字符串 | 最长相等前后缀 |
a | 0 |
aa | 1 |
aab | 0 |
aaba | 1 |
aabaa | 2 |
aabaaf | 0 |
那么我们就得到了aabaaf的前缀表了
a | a | b | a | a | f |
0 | 1 | 0 | 1 | 2 | 0 |
使用前缀表
我们可以发现,a对应的前缀表是2,而j指针刚好也跳转到了2坐标,是因为这是前缀表,所以2代表的长度一定是从第一个字符数起的,那么要跳转到对应部分的下一个坐标,就是2,因为坐标是从0开始的。
我们通常用next数组来存前缀表,为什么叫next数组就是因为它也代表了j要退回的位置.
next[i]含义:1.存的是以i为终点的字符串中的最长相同的前缀和后缀的长度
2.也代表了下一次要跳转的位置
2.实现next
首先我们先看看我们有了next数组后,字符串是怎么匹配的吧
代码:
int j=0;
for(int i=0;i<haystack.length();i++) {
while(j>0&&needle.charAt(j)!=haystack.charAt(i)) { //不匹配j回退 此处j>0代表退无可退
j=next[j-1];
}
if(needle.charAt(j)==haystack.charAt(i)) { 匹配成功j++
j++;
}
if(j==needle.length()) {
//匹配成功
}
}
其实next数组的求法就是模式串与自己匹配出来的,所以代码是一样的
代码:
next[0]=0;
int j=0;
for(int i=1;i<needle.length();i++) {
while(j>0&&needle.charAt(i)!=needle.charAt(j)) {
j=next[j-1];
}
if(needle.charAt(i)==needle.charAt(j)) {
j++;
}
next[i]=j;
}
}
我们仔细想想next数组要怎么求
3.全部代码
class Solution {
int[] next;
public void strStr(String haystack, String needle) {
next=new int[needle.length()];
getNext(needle);
int j=0;
for(int i=0;i<haystack.length();i++) {
while(j>0&&needle.charAt(j)!=haystack.charAt(i)) {
j=next[j-1];
}
if(needle.charAt(j)==haystack.charAt(i)) {
j++;
}
if(j==needle.length()) {
//匹配成功
}
}
}
public void getNext(String needle) {
next[0]=0;
int j=0;
for(int i=1;i<needle.length();i++) {
while(j>0&&needle.charAt(i)!=needle.charAt(j)) {
j=next[j-1];
}
if(needle.charAt(i)==needle.charAt(j)) {
j++;
}
next[i]=j;
}
}
}