「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
问题
模式匹配
朴素解法
朴素解法很简单,两重循环即可解决问题。
kmp
计原串为Str,匹配串为str。
kmp算法在朴素算法上做了改进,我们知道如果在下标i处失配,那么前面的str[0:i-1]是完全匹配的,那么如果我们知道str[0:i-1]的最长公共前后缀,那么我们就可以知道下次匹配的最佳位置了,而不是像朴素解法那样只移动一个单位。
为了方便起见,这里使用一个next数组进行数据保留,next[i]表示以i为结尾的最长公共前后缀中的前缀的尾下标的后一个位置(也就是在i+1处失配后的下一次进行模式匹配的位置)。
于是问题规约为next数组的求解。
那么next数组如何求解呢?
答案是前后缀匹配。
问题的求解还是匹配,不过这种匹配还是简单的,因为可以通过迭代的方式得到ans.
我们记j表示前缀的尾下标,i表示后缀的尾下标,那么显然可以知道起始状态i=1,j=0。
比如说有匹配串aaabeabf
j=0 aaabeabf
i=1 aabeabf
也就是这两个字符串进行模式匹配。
如果str[i]==str[j],此时next[i]=j+1前后缀都扩大,i++,j++。显然这里我们将得到
前缀aa(0到1),和后缀aa(1到2)。
如果str[i]!=str[j],此时j应该返回next[j-1]的位置,j=next[j-1],因为j-1之前是完全匹配的,这样就有效地利用了最长公共前后缀,这里前缀将变为a,后缀将变为a,此时在比较str[j]和str[i]是否相同,相同则进行之前str[i]==str[j]的操作,否则还需要继续往前找前缀。
例题
class Solution {
public int strStr(String haystack, String needle) {
if(needle.equals(""))return 0;
int[] next=new int[needle.length()];
int j=0;
next[0]=0;
//第一次匹配得到next数组
for(int i=1; i<next.length; i++){
if(needle.charAt(i)==needle.charAt(j)){
next[i]=j+1;
}else{
while(needle.charAt(i)!=needle.charAt(j)){
if(j==0){
j=-1;
break;
}
j=next[j-1];
}
next[i]=j+1;
}
j++;
}
j=0;
//第二次匹配得到匹配串在模式串的起始下标
for(int i=0; i<haystack.length(); i++){
if(haystack.charAt(i)==needle.charAt(j)){
if(j==needle.length()-1){
return i-needle.length()+1;
}
}else{
while (haystack.charAt(i)!=needle.charAt(j)){
if(j==0){
j=-1;
break;
}
j=next[j-1];
}
}
j++;
}
return -1;
}
}