题目28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
思路
haystack = "aabaabaaf"
needle = "aabaaf"
暴力解法,每次怎样找到之前匹配过的内容?
f不匹配,此时,寻找f前面aabbaa的最长相等前后缀,也就是2,这个2告诉我们,f前面的字符串存在着前缀aa和后缀aa相等,并且这是最长的相等前后缀;理解这一点之后,再来看此时指针应该怎么移动:
- 若不使用kmp算法,f不匹配,指针应该移动到0;指向文本串的指针后移一位
- 使用kmp算法,f不匹配,但f前面的aabaa已经匹配上了,这里隐含着,f前面的aa(相等前后缀)已经匹配上了,我们不能浪费这个信息,此时指针不用回退到开头,可以直接从b开始,看看b是否和文本串匹配,继续向后遍历
总结
KMP算法最大化地使用已经匹配过的字符串信息,不浪费任何一个相等前后缀
- 当字符不匹配时,文本串不回退,通过prefix确定模式串回退位置,即模式串的下一个查找值,时间复杂度是o(m)
- prefix的计算,利用双指针,时间复杂度同样是o(n)
- KMP算法复杂度是o(m) + o(n),暴力解法是o(m*n)
代码
- 计算前缀表-prefix数组
// 求前缀表
public int[] getPerfix(String s) {
int[] prefix = new int[s.length()];
prefix[0] = 0;
if(s.length() < 2) {
return prefix;
}
// int fast = 1;//后缀末尾
int slow = 0;//前缀末尾
//abcdabcx
for(int fast = 1;fast < s.length();fast++) {
// 不相等的情况要一直回退,所以用while
while(slow > 0 && s.charAt(fast) != s.charAt(slow)){
// 不相等的话slow要回退到fast前一个指定的位置
slow = prefix[slow-1];
}
// 相等的情况
if(s.charAt(fast) == s.charAt(slow)) {
//相等前后缀长度为slow+1
slow++;
}
prefix[fast] = slow;
}
return prefix;
}
- 匹配字符串
public int strStr(String haystack, String needle) {
int flag = -1;
if(haystack.length() < needle.length()) return flag;
int[] prefix = getPerfix(needle);
int fast = 0;//在haystack中遍历,不会退
int slow = 0;//在slow中遍历,根据prefix回退
for(;fast < haystack.length();fast++) {
// 不相等的情况要一直回退,所以用while
while(slow > 0 && haystack.charAt(fast) != needle.charAt(slow)){
// 不相等的话slow要回退到fast前一个指定的位置
slow = prefix[slow-1];
}
// 相等的情况
if(haystack.charAt(fast) == needle.charAt(slow)) {
//相等长度为slow+1
slow++;
}
if(slow == needle.length()) {
return fast-slow+1;
}
}
return flag;
}
问题:以上代码用到了字符串charAt()方法,所以速度比较慢,下面改为字符数组char[]的写法
class Solution {
public int strStr(String haystack, String needle) {
char[] h = haystack.toCharArray();
char[] n = needle.toCharArray();
int[] prefix = getPrefix(needle);
int slow = 0;
for(int fast = 0;fast < h.length;fast++) {
//不相同情况
while(slow > 0 && h[fast] != n[slow]) {
slow = prefix[slow - 1];
}
if(h[fast] == n[slow]) {
slow++;
}
if(slow == n.length) {
return fast - slow + 1;
}
}
return -1;
}
public int[] getPrefix(String s) {
char[] ch = s.toCharArray();
int slow = 0;
int[] prefix = new int[ch.length];
prefix[0] = 0;
if(ch.length < 2) return prefix;
for(int fast = 1;fast < ch.length; fast++) {
// 不相等情况
while(slow > 0 && ch[slow] != ch[fast]) {
slow = prefix[slow-1];
}
// 相等情况
if(ch[slow] == ch[fast]) {
slow++;
}
prefix[fast] = slow;
}
return prefix;
}
}