常规kmp
题目:给你一个非空模板串S,一个文本串T,问T在S中完全匹配的起始下标(从0开始)
分析:
利用
match串的next数组加速匹配过程next[i]:表示match[0...i-1]位置上前缀和后缀最长匹配长度规定:
next[0]=-1.next[1]=0,遍历指针从i=2开始
public static int[] getNextArray(char[] match) {
// 长度为1的数组,前面没有元素,规定next值为-1
if (match.length == 1) {
return new int[]{-1};
}
int[] next = new int[match.length];
// 第一个位置前面没有元素,规定为-1
next[0] = -1;
// 第二个位置前面只有0位置的元素,由于任何子串的后缀不能包括第一个字符,规定为0
next[1] = 0;
int i = 2;// 遍历指针从第3个元素开始
// cn两个含义:
// 1.拿哪个位置的字符跟i-1比,由于i初始化2,i-1是1,所以cn=0
// 2.i位置的前缀和后缀最大匹配长度=最大匹配下标+1的值
int cn = 0;
while (i < next.length) {
if (match[i - 1] == match[cn]) {// i-1位置和cn相同,next[i]=cn+1,cn和i后移再匹配
next[i++] = ++cn;
} else if (cn > 0) {// i-1位置和cn位置不相同:有两种情况
// 情况1:cn一直往前跳,但cn必须大于数组0位置
cn = next[cn];
} else {// 情况2:cn一直往前跳来到0位置,说明match[i]无前后缀匹配,next[i++]=0
next[i++] = 0;
}
}
return next;
} - kmp:返回match串在s1中完整匹配的起始下标
public class KMP {
// KMP:字符串s和m,s是否包含m,如果包含返回m在s中开始的位置?再结合NC149_kmp算法做比较
// 要求整体时间复杂度O(N),N是s1的长度,M是s2的长度
public static int getIndexOf(String s, String m) {
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
return -1;
}
char[] str1 = s.toCharArray();
char[] str2 = m.toCharArray();
int i1 = 0;
int i2 = 0;
// 获得待匹配str2的next数组
int[] next = getNextArray(str2);
while (i1 < str1.length && i2 < str2.length) {
if (str1[i1] == str2[i2]) {// i1与i2都匹配成功,与暴力法一样都后移指针
i1++;
i2++;
} else if (next[i2] == -1) {// m串的遍历指针来到next[i2]=-1的位置,表示无法加速匹配,只能暴力移动i1
i1++;
} else {// 只要不是-1,i2就加速移动到最长后缀下一个位置,由于数组下标是0开始,所以i2移动到next[i2]下标
i2 = next[i2];
}
}
// while结束,i1或者i2越界
// s1:abab,i1越界,匹配失败
// s2: bab,i2越界,代表匹配完成(i1,i2是同时移动的),匹配的起始位置=i1-i2
return i2 == str2.length ? i1 - i2 : -1;
}
// 获得match串的next数组
public static int[] getNextArray(char[] match) {
// 长度为1的数组,前面没有元素,规定next值为-1
if (match.length == 1) {
return new int[]{-1};
}
int[] next = new int[match.length];
// 第一个位置前面没有元素,规定为-1
next[0] = -1;
// 第二个位置前面只有0位置的元素,由于任何子串的后缀不能包括第一个字符,规定为0
next[1] = 0;
int i = 2;// 遍历指针从第3个元素开始
// cn两个含义:
// 1.拿哪个位置的字符跟i-1比,由于i初始化2,i-1是1,所以cn=0
// 2.i位置的前缀和后缀最大匹配长度=最大匹配下标+1的值
int cn = 0;
while (i < next.length) {
if (match[i - 1] == match[cn]) {// i-1位置和cn相同,next[i]=cn+1,cn和i后移再匹配
next[i++] = ++cn;
} else if (cn > 0) {// i-1位置和cn位置不相同:有两种情况
// 情况1:cn一直往前跳,但cn必须大于数组0位置
cn = next[cn];
} else {// 情况2:cn一直往前跳来到0位置,说明match[i]无前后缀匹配,next[i++]=0
next[i++] = 0;
}
}
return next;
}
public static void main(String[] args) {
String str = "abcabcababaccc";
String match = "ababa";
// next = [-1, 0, 0, 1, 2]
// System.out.println(Arrays.toString(getNextArray(match.toCharArray())));
System.out.println(getIndexOf(str, match));// 返回完全开始匹配的索引
}
}
牛客NC149
题目:给你一个非空模板串S,一个文本串T,问S在T中出现了多少次
示例:
输入: "ababab","abababab" 返回值: 2
分析:
由于是问返回S在T中出现了多少次?常规Kmp第一次匹配就返回下标,所以要改造next数组和接口逻辑
改造常规的next
// 获得match串的改造next数组,比常规next多一位数
private int[] getNext(char[] match) {
if (match.length == 1) {
return new int[]{-1};
}
// next改造1:next多算一个,多出的末尾数组记录整个ms的最长前后缀长度
int[] next = new int[match.length + 1];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
// next改造2:i<m.len改成i<=m.len
while (i <= match.length) {
if (match[i - 1] == match[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
} kmp:
- 改造了
next数组,当i2越界,代表匹配上了,res+1;并且i2 = next[i2]回退到整个s2的最长后缀位置开始二轮匹配
- 改造了
public class Solution {
// 牛客149kmp:给你一个非空模板串S,一个文本串T,问S在T中出现了多少次
public int kmp(String S, String T) {
if (T == null || S == null || S.length() < 1 || T.length() < S.length()) {
return 0;
}
char[] s1 = T.toCharArray();
char[] s2 = S.toCharArray();
int i1 = 0;
int i2 = 0;
int[] next = getNext(s2);
int res = 0;
while (i1 < s1.length && i2 < s2.length) {
if (s1[i1] == s2[i2]) {
i1++;
i2++;
} else if (next[i2] == -1) {
i1++;
} else {
i2 = next[i2];
}
// 以上是常规kmp算法,后续是改造了3点
// 改造1:i2越界,代表匹配上了,结果+1
// getNext中改造2:next多算一个,多出的末尾数组记录整个s2的最长前后缀长度
if (i2 == s2.length) {
res++;
// i2再回退到整个s2的最长后缀位置开始二轮匹配
i2 = next[i2];
}
}
// 改造2:返回个数
return res;
}
// 获得match串的改造next数组,比常规next多一位数
private int[] getNext(char[] match) {
if (match.length == 1) {
return new int[]{-1};
}
// next改造1:next多算一个,多出的末尾数组记录整个ms的最长前后缀长度
int[] next = new int[match.length + 1];
next[0] = -1;
next[1] = 0;
int i = 2;
int cn = 0;
// next改造2:i<m.len改成i<=m.len
while (i <= match.length) {
if (match[i - 1] == match[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
public static void main(String[] args) {
String S = "abab";
String T = "abacabab";
// 返回match子串在str中匹配的起始位置
Solution solution = new Solution();
System.out.println(solution.kmp(S, T));
}
}

京公网安备 11010502036488号