今日任务
●28. 实现 strStr()
●459.重复的子字符串
●字符串总结
●双指针回顾
KMP算法
认识前缀表
- KMP算法较于暴力解法的优化:KMP算法简化了查找过程中的遍历次数
- 暴力解法中当遇到冲突时,是将模式串后移一位,然后再和文本串进行比较,其事件复杂度为O(M*N)。
- KMP算法为当遇到冲突时,下一次开始位置为:冲突位之前的字符串的最长相等前后缀的前缀的下一位开始
- KMP算法为什么要使用前缀表:前缀表中记录了不匹配字符与之前所有字符构成的字符串的所有字串子串的最长相等前后缀的值
- 什么是前缀,什么是后缀:
- 前缀为包含首字符但不包含包含尾字符的所有子串
- 后缀为不包含首字符但包含尾字符的所有字串
如:模式串为 a a b a a f,其所有子串的前后缀及最长相等前后缀的长度如下:
前缀表(next数组):前缀表中记录了一个字符串的所有子串的最长相等前后缀的值
如aabaaf的前缀表为[0,1,0,1,2,0],而遇到冲突时下一次的开始位置下标为:冲突位的前一位对应前缀表的值
如匹配aabaaf时在f位发生冲突,下一次开始匹配位的下标为 f前的字符a对应的next数组值:2,因此从开始匹配(遇到冲突时指向文本字符串位置的指针不动)
实现一个字符串的前缀表
解过程为以下四部:
- 初始化:初始化指向前缀末尾的指针值、指向后缀末尾的指针值、前缀表next数组
- 处理前缀末尾值与后缀末尾数组不相等的情况
- 处理前缀末尾值与后缀末尾值相等的情况
- 更新next数组
实现过程:
//求模式串的前缀表组成的next数组
public static int[] nextArray(String str) {
char[] chars = str.toCharArray();
//初始化:
// j:指向前缀的末尾,且代表i(包括i)之前的子串最长相等前后缀的长度;
// i指向后缀的末尾
int[] next = new int[chars.length];
int j = 0;
//第一个字符组成的前缀的最大相等前后缀的长度为0
next[0] = 0;
//比较子串的最长前缀和最长后缀的最后一个字符是否相等
for (int i = 1; i < chars.length; i++) {
//前后缀不相等的情况
while (j > 0 && chars[i] != chars[j]) {
//j向后回退,回退位置为冲突位的前一位对应的next数组的值
j = next[j - 1];
}
//前后缀相等的情况
if (chars[i] == chars[j]) {
j++;
}
//更新next的值
next[i] = j;
}
return next;
}
KMP算法匹配子串
//KMP算法匹配子串
public static int strStr1(String haystack, String needle) {
//求needle子串的前缀表
int[] next = nextArray(needle);
//查找是否存在子串
char[] charh = haystack.toCharArray();
char[] charn = needle.toCharArray();
int a = 0;
int b = 0;
while (a < charh.length && b < charn.length) {
//匹配文本串与模式串的第一位字符相等的位置
if (b == 0 && charn[b] != charh[a]) {
a++;
}
//遇见冲突,模式串的指针回退,回退位置为:冲突位的前一位对应next数组中的值;文本串的指针站立不动
else if (b != 0 && charn[b] != charh[a]) {
b = next[b - 1];
}
//无冲突(模式串指针和文本串对应字符相等):两个指针一致前进
else if (charn[b] == charh[a]) {
b++;
a++;
}
}
//跳出循环时a值等于文本串的长度,b值等于模式串的长度
if (b == charn.length) {
return a - b;
}
return -1;
}
- 28.实现 strStr()
暴力解决:O(M*N)
public class KMPSubString {
public static int strStr(String haystack, String needle) {
char[] w = haystack.toCharArray();
char[] m = needle.toCharArray();
int ws = w.length;
int ms = m.length;
for (int i = 0; i < ws - ms + 1; i++) {
int a = i;
int b = 0;
while (b < ms && w[a] == m[b]) {
a++;
b++;
}
if (b == ms) {
return i;
}
}
return -1;
}
KMP算法匹配子串
public static int strStr1(String haystack, String needle) {
//求needle子串的前缀表
int[] next = nextArray(needle);
//查找是否存在子串
char[] charh = haystack.toCharArray();
char[] charn = needle.toCharArray();
int a = 0;
int b = 0;
while (a < charh.length && b < charn.length) {
//匹配文本串与模式串的第一位字符相等的位置
if (b == 0 && charn[b] != charh[a]) {
a++;
}
//遇见冲突,模式串的指针回退,回退位置为:冲突位的前一位对应next数组中的值;文本串的指针站立不动
else if (b != 0 && charn[b] != charh[a]) {
b = next[b - 1];
}
//无冲突(模式串指针和文本串对应字符相等):两个指针一致前进
else if (charn[b] == charh[a]) {
b++;
a++;
}
}
//跳出循环时a值等于文本串的长度,b值等于模式串的长度
if (b == charn.length) {
return a - b;
}
return -1;
}
//求模式串的前缀表组成的next数组
public static int[] nextArray(String str) {
char[] chars = str.toCharArray();
//初始化 j:指向前缀的末尾,且代表i(包括i)之前的子串最长相等前后缀的长度;
// i指向后缀的末尾
int[] next = new int[chars.length];
int j = 0;
//第一个字符组成的前缀的最大相等前后缀的长度为0
next[0] = 0;
//先比较子串的最长前缀和最长后缀的最后一个字符是否相等
for (int i = 1; i < chars.length; i++) {
//前后缀不相等的情况
while (j > 0 && chars[i] != chars[j]) {
//j向后回退,回退位置为冲突位的前一位对应的next数组的值
j = next[j - 1];
}
//前后缀相等的情况
if (chars[i] == chars[j]) {
j++;
}
//更新next的值
next[i] = j;
}
return next;
}
}
相关题目:(明日KMP)
459.重复的子字符串