目录
一、KMP算法有什么用?
该算法主要应用在字符串匹配上,当模式串与文本串不匹配时,由于知道一部分已经匹配过的内容,可以避免从头再去匹配
如:文本串:aabaabaaf,模式串:aabaaf。
在匹配过程中'b'与'f'不匹配,暴力思想就是直接让模式串从文本串第二个'a'处重新匹配,时间复杂度O(m * n);而使用KMP算法,将会使模式串下标为2的'b'直接与文本串下标为5的'b'进行匹配,时间复杂度O(m + n)
二、构建next数组(就是前缀表)
1)什么是前缀表(next数组)
记录模式串下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
2)前缀表有什么用
用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配
3)前缀表怎么记录的?
计算模式串每一个子串的最大相等前后缀,其中前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
例如:模式串:aabaaf
子串a:无前后缀,最大相等前后缀长度为0
子串aa:前缀'a',后缀'a',最大相等前后缀'a',长度为1
子串aab:前缀'a','aa',后缀'b','ab',最大相等前后缀长度为0
子串aaba:前缀'a','aa','aab',后缀'a','ba','aba',最大相等前后缀'a',长度为1
子串aabaa:前缀'a','aa','aab'.'aaba',后缀'a','aa','baa','abaa',最大相等前后缀'aa',长度为2
子串aabaa:前缀'a','aa','aab'.'aaba'.'aabaa',后缀'af','aaf','baaf','abaaf',最大相等前后缀为0
0 1 0 1 2 0 就是模式串aabaaf的前缀表(也是我们需要构建的next数组)
4)为什么一定要用前缀表
例子:文本串:aabaabaaf,模式串:aabaaf。
下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀'aa'的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。
5)构建next数组
例子:模式串:aabaaf
指针i:1、用于标记待计算子串后缀末尾字符位置
2、[0,i]区间字符组成的就是待计算子串
指针j:1、表示的是当前待计算子串最大相等前缀的后一位(下次循环可以直接与i位置字符进行比较)
2、也表示当前带计算子串最大相等前后缀的长度
(i = 1,待计算子串就是'aa',循环过后,j = 1,指向第二个'a',也就是'aa'最大相等前缀的后一位;同时,'aa'的最大相等前后缀的长度也是1)
(i = 4,待计算子串就是'aabaa',循环过后,j = 2,指向'b',也就是'aabaa'最大相等前缀的后一位;同时,'aabaa'的最大相等前后缀的长度也是2)
public void getNext(int[] next, String s){
int j = 0;
next[0] = 0;
for (int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(i) != s.charAt(j)){ // j要保证大于0,因为下面有取j-1作为数组下标的操作
j=next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
}
if(s.charAt(i) == s.charAt(j)){
j++;
}
next[i] = j;//填充next数组
}
}
三、力扣28.实现 strStr()
题目链接:28. 找出字符串中第一个匹配项的下标
---给你两个字符串 haystack
和 needle
,请你在 haystack
字符串中找出 needle
字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle
不是 haystack
的一部分,则返回 -1
。
思路:KMP算法的基础题
class Solution {
public int strStr(String haystack, String needle) {
int[] next = getNext(needle);
//这里采用for循环,必须先判断不相等,再判断相等的情况,不然循环结束进行i++操作,而j并没有进行加加操作,从而
//导致当前j坐标的字符串又跟i++之后的字符进行匹配
for(int i = 0,j = 0; i < haystack.length(); i++){
while(j > 0 && needle.charAt(j) != haystack.charAt(i))
j = next[j - 1];
if(needle.charAt(j) == haystack.charAt(i)){
j++;
if(j == needle.length())return i + 1 - j;
}
}
return -1;
}
//构建模式串的next数组
public int[] getNext(String needle){
//定义一个next数组
int[] next = new int[needle.length()];
int j = 0;
next[0] = j;
//采用for循环,必须先判断不相等,再判断相等,不然循环结束后进行i++操作,而j并没有加加,导致j更新不及时出错
for(int i = 1; i < needle.length(); i++){
while(j > 0 && needle.charAt(j) != needle.charAt(i)){
j = next[j - 1];
}
if(needle.charAt(j) == needle.charAt(i))
j++;
next[i] = j;
}
return next;
}
}
四、拓展题
重复的子字符串
题目链接:459. 重复的子字符串
---给定一个非空的字符串 s
,检查是否可以通过由它的一个子串重复多次构成。
思路:在由重复子串组成的字符串中,最长相等前后缀不包含的子串就是最小重复子串,这里拿字符串s:abababab 来举例,ab就是最小重复单位
正是因为 最长相等前后缀的规则,当一个字符串由重复子串组成的,最长相等前后缀不包含的子串就是最小重复子串。所以,我们只需判断该字符串长度 % 最长相等前后缀不包含的子串长度是否为0即可(先排除最大相等前后缀长度为0的情况),若为0,返回true;反之,返回false。
class Solution {
public boolean repeatedSubstringPattern(String s) {
int[] next = getNext(s);
int lenS = s.length();
if(next[lenS - 1] != 0 && lenS % (lenS - next[lenS - 1]) == 0){
return true;
}
return false;
}
public int[] getNext(String s){
int j = 0;
int[] next = new int[s.length()];
for(int i = 1; i < s.length(); i++){
while(j > 0 && s.charAt(i) != s.charAt(j))
j = next[j - 1];
if(s.charAt(i) == s.charAt(j))
j++;
next[i] = j;
}
return next;
}
}
标签:子串,day09,前缀,后缀,next,int,算法,KMP,charAt
From: https://blog.csdn.net/2301_76909842/article/details/139800180