JAVA二刷-Day9 |KMP: 28. 实现 strStr(), 459.重复的子字符串
KMP详解
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。因此,重点在于如何记录已经匹配的文本内容?
核心在于引入记录索引的地址,即可以看做,如果前面存在已经匹配的内容了,我们应该通过一个索引值来确定已经匹配内容的位置,并以这个位置重新进行迭代。记录这个索引的方式就叫做前缀表(prefix table: next),前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。它的记录方式是记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
如何理解这个概念呢?举一个例子:
要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。
此时当搜索到第二个aab时,与aaf冲突,此时观察前面的最大相同前后缀的aa,即此时已经所搜索的后缀aa,可以直接当做下一次查询的前缀来使用。因此,只需要从b字符开始继续与文本串进行对比即可。
下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。
因此,前缀表可以看成存储到目前索引为止的字符串,最长相等前缀的长度值。
实现strStr()
LeetCode题目链接:https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/
解题思路
利用KMP思想,重点在于实际应用简化时,索引值可以取开始-1,以方便实际的应用。以及getNext的取值方法,当开始值为-1时候,可以在迭代时更加方便判定。同时,注意在next数组构造中,i取1开始,因为0默认初始化为-1,没有前缀和。同时此时对比才是交错对比,而不会出现i和j对比的是同一个值的情况。
代码如下:
class Solution {
public void getNext(String str, int[] next) {
char[] charArr = str.toCharArray();
int j = -1;
next[0] = -1;
for (int i = 1; i < charArr.length; i++) {
while ( j >= 0 && charArr[i] != charArr[j + 1] ) {
j = next[j];
}
if (charArr[i] == charArr[j + 1]) {
j++;
}
next[i] = j;
}
}
public int strStr(String haystack, String needle) {
int[] next = new int[needle.length()];
getNext(needle, next);
for (int i = 0, j = -1; i < haystack.length(); i++) {
while ( j >= 0 && haystack.charAt(i) != needle.charAt(j + 1)) {
j = next[j];
}
if (haystack.charAt(i) == needle.charAt(j + 1)) {
j++;
}
if (j == needle.length() - 1) return i - j;
}
return -1;
}
}
重复的子字符串
LeetCode题目链接:https://leetcode.cn/problems/repeated-substring-pattern/
解题思路
需要注意的是,如果存在公共重复子串,那么理解为最大公共前后缀长度等于总长度减去子串所具有的前缀和的长度。
同时注意,此时最后结束的数字一定不是-1,因为至少子串有一次重复。
具体代码如下:
class Solution {
public void getNext(String str, int[] next) {
int j = -1;
next[0] = -1;
for (int i = 1; i < str.length(); i++) {
while (j >= 0 && str.charAt(i) != str.charAt(j + 1)) {
j = next[j];
}
if (str.charAt(i) == str.charAt(j + 1)) {
j++;
}
next[i] = j;
}
return;
}
public boolean repeatedSubstringPattern(String s) {
int[] next = new int[s.length()];
getNext(s, next);
if (next[s.length() - 1] != -1 && (s.length() % (s.length() - next[s.length() - 1] - 1) == 0))
return true;
return false;
}
}