KMP算法
有关KMP算法的理论基础可查阅,有图文表示和较为详细的解释:《代码随想录》KMP算法
28. 实现 strStr()
实现 strStr()的关键在于利用KMP算法的解题思想,求出模版字符串的前缀表;那么什么是前缀表呢?
- 前缀表是一个数组,常称为next数组或prefix数组。
- 前缀表中存储的是该字符串在每一个字符之前的字符串中,最长相等前后缀的长度
- 即
下标i之前(包括i)的字符串中,相等的前缀后缀的最大长度
,同时亦是已匹配的最大长度的下标位置 - 遍历完模版字符串后,即可得到模版字符串的前缀表
注意:获取Next数组,即获取前缀表的过程,只与模版字符串相关,不需要目标字符串的参与。
得到模版字符串的前缀表后,我们就可以利用它来与目标字符串进行匹配:
- 按顺序比较模版字符串与目标字符串的每个字符
- 当遇到不同的字符时,通过前缀表来回退指针,可以快速定位到已匹配的最大长度的下标位置
- 已经匹配过的字段就不需要再进行匹配了,所以直接从定位到的下标位置开始继续匹配即可
- 因此利用前缀表可以大大提升匹配效率
- 当遇到相同字符时,则同时移动指针,继续依次比较字符。
- 当模版字符串遍历结束时,则说明目标字符串中包含模版字符串
- 通过目标字符串的当前指针下标,减去模版字符串长度 + 1,就可以得到匹配字符串的起始位置
- 反之如果目标字符串先遍历完,则说明字符串匹配失败,返回 -1
注意:匹配字符串的过程中,只是利用前缀表,并不需要对前缀表进行任何操作。
- 当字符串被确定时,它的前缀表也会被确定,是具备唯一性的。
- 所以通过模版字符串得到其前缀表后,可以利用模版字符串和它的前缀表来与任何的字符串进行匹配验证,并不需要重新获取。
Java实现(原始前缀表):
class Solution {
// getNext:求KMP算法中的next数组,遍历字符串,保存当前下标的最长相等前后缀长度
public int[] getNext(String str){
char[] arr = str.toCharArray();
int[] next = new int[str.length()];
// 首字符的最长相等前后缀长度一定为0
next[0] = 0;
for(int i = 1, j = 0; i < next.length; i++){
while(j > 0 && arr[i] != arr[j]){
j = next[j - 1];
}
if(arr[i] == arr[j]){
j++;
}
next[i] = j;
}
return next;
}
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);
char[] s = haystack.toCharArray(), t = needle.toCharArray();
for(int i = 0, j = 0; i < s.length; i++){
while(j > 0 && s[i] != t[j]){
j = next[j - 1];
}
if(s[i] == t[j]){
j++;
}
if(j == t.length){
return (i - t.length + 1);
}
}
return -1;
}
}
原始前缀表 和 前缀表统一减一 的思路是异曲同工的,只是实现的过程不同:
- 原始前缀表的next数组下标从0开始
- 前缀表统一减一后的next数组下标从-1开始
Java实现(前缀表统一减一):
class Solution {
// getNext:求KMP算法中的next数组,遍历字符串,保存当前下标的最长相等前后缀长度
public int[] getNext(String str){
char[] arr = str.toCharArray();
int[] next = new int[str.length()];
// 首字符的最长相等前后缀长度一定为0,统一减1后,下标从-1开始
next[0] = -1;
for(int i = 1, j = -1; i < next.length; i++){
while(j >= 0 && arr[i] != arr[j + 1]){
j = next[j];
}
if(arr[i] == arr[j + 1]){
j++;
}
next[i] = j;
}
return next;
}
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);
char[] s = haystack.toCharArray(), t = needle.toCharArray();
for(int i = 0, j = -1; i < s.length; i++){
while(j >= 0 && s[i] != t[j + 1]){
j = next[j];
}
if(s[i] == t[j + 1]){
j++;
}
if(j == t.length - 1){
return (i - t.length + 1);
}
}
return -1;
}
}
459.重复的子字符串
字符串总结
双指针回顾
本来以为字符串的题目应该都是非常简单的,但是从未接触过的KMP算法再次把我从幻想中拉了出来,实在是高深莫测呀!
山重水复疑无路,柳暗花明又一村。