一、BF算法
使用子串与主串匹配,匹配失败就退回这一趟最开始匹配的主串位的下一位继续匹配,需要的时间复杂度是O(m * n),m为子串长度,n为主串长度。
二、KMP
(1)什么是前缀表
使用前缀表,记录每个字符的最长相等前后缀的长度。
特性:在遇到不匹配的位置的时候,找前一位的子串它的最长相等前后缀的长度,将子串下标回退到长度对应的位置。(选择前缀表的原因)
前缀表有什么作用:用来回溯的,它记录了模式串和主串不匹配的时候,模式串应该从哪里开始重新匹配。前缀表里的数值代表这就是:当前位置之前的子串有多大长度相等的前缀后缀。
什么是前缀:包含首字母,不包含尾字母的子串
比如aabaaf中,f的前缀是a,aa,aab,aaba,aabaa
什么是后缀:包含尾字符,不包含首字符的子串
比如aabaaf中,f 的后缀是f,af,aaf,baaf,abaaf。
原始的前缀表:
a a b a a f
0 1 0 1 2 0
(2)求next数组
1.初始化
2.处理前后缀不相同的情况
3.处理前后缀相同的情况
4.赋值next数组
使用两个指针:
- i 表示后缀末尾位置;
- j 表示前缀末尾位置,同时也是最长相等前后缀的长度。
(3)前缀表的变形
常见的前缀表变形为原始前缀表元素统一减1,或者右移一位,第一位以-1填充。
不管是哪种方法,甚至是直接使用元素的前缀表,都是可以使用KMP算法,关键在于如何去使用next,如果是使用原始前缀表的话,在遇到匹配不成功的情况,将子串的下标移动到当前字符的前一位字符的next[ j - 1 ]即可,如果 j=0,则主串下标加1即可
(4)求原始next数组
// 求原始前缀表
public void getNext(String pattern, int[] next) {
if(pattern == null || pattern.length() == 0) {
return ;
}
int i; // 后缀末尾位置
int j = 0; // 前缀末尾位置,同时也是最长相等前后缀长度
next[0] = 0;
// 遍历子串长度
for(i = 1; i < pattern.length(); i++) {
while(j > 0 && pattern.charAt(i) != pattern.charAt(j)) {
j = next[j - 1]; // 如果不匹配,则子串下标移动到前一位的最长前后缀相等长度
}
if(pattern.charAt(i) == pattern.charAt(j)) {
j++; // 匹配的子串长度加一
next[i] = j; // 更新next数组的值
}
}
}
(5)使用原始前缀表进行主子串字符匹配
// 使用原始前缀表进行主子串字符匹配
public int indexOf(String target, String pattern) {
// 题目要求子串为空字符串时,返回0
if(pattern.length() == 0) {
return 0;
}
int[] next = new int[pattern.length()];
// 如果子串长度大于主串长度,则返回-1,表示主串不包含子串
if(pattern.length() > target.length()) {
return -1;
}
getNext(pattern, next); // 获取前缀表next数组
int n = target.length(); // 主串长度
int m = pattern.length(); // 子串长度
int i = 0; // 遍历主串的下标
int j = 0; // 遍历子串的下标
// 只要主串或子串遍历完成,则退出循环
while(i < n && j < m) {
// 如果主串和子串字符匹配成功,下标同时加一
if(target.charAt(i) == pattern.charAt(j)) {
i++;
j++;
} else {
// 如果匹配不成功,且子串下标为0,则主串下标加一,否则子串下标移动到前一个字符的最大相等前后缀长度
if(j == 0) {
i++;
} else {
j = next[j - 1];
}
}
}
return j == m ? i - m : -1; // 如果匹配成功,返回起始子串下标
}
三、实现 strStr()
力扣练习题: leetcode-cn.com/problems/im…